diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 642d998a0..d5e383d95 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -154,6 +154,7 @@ set(cockatrice_SOURCES src/interface/palette_editor/palette_generator.cpp src/interface/palette_editor/quick_setup_panel.cpp src/interface/palette_editor/palette_grid_widget.cpp + src/interface/palette_editor/font_grid_widget.cpp src/interface/palette_editor/palette_editor_dialog.cpp src/interface/widgets/cards/additional_info/color_identity_widget.cpp src/interface/widgets/cards/additional_info/deck_color_identity.cpp diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 08eccf1e4..e1acb67da 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -137,6 +137,9 @@ SettingsCache::SettingsCache() // before that rename (idempotent, safe to run on every startup, portable builds included). SettingsMigration::migrateGameFiltersToCamelCase(settingsPath); + // Retire appearance/maxFontSize into per-theme CardText max (idempotent, safe every startup). + SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath); + shortcutsSettings = new ShortcutsSettings(settingsPath, this); cardDatabaseSettings = new CardDatabaseSettings(settingsPath, this); serversSettings = new ServersSettings(settingsPath, this); diff --git a/cockatrice/src/client/settings/shortcut_treeview.cpp b/cockatrice/src/client/settings/shortcut_treeview.cpp index 4a5bafb01..2501798cb 100644 --- a/cockatrice/src/client/settings/shortcut_treeview.cpp +++ b/cockatrice/src/client/settings/shortcut_treeview.cpp @@ -1,5 +1,6 @@ #include "shortcut_treeview.h" +#include "../../interface/theme_manager.h" #include "cache_settings.h" #include "shortcuts_settings.h" @@ -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); diff --git a/cockatrice/src/filters/filter_tree_model.cpp b/cockatrice/src/filters/filter_tree_model.cpp index 9b55ce2fd..66fe39821 100644 --- a/cockatrice/src/filters/filter_tree_model.cpp +++ b/cockatrice/src/filters/filter_tree_model.cpp @@ -1,5 +1,7 @@ #include "filter_tree_model.h" +#include "../interface/theme_manager.h" + #include #include #include @@ -39,6 +41,12 @@ FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent) connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow); connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow); connect(fTree, &FilterTree::postRemoveRow, this, &FilterTreeModel::proxyEndRemoveRow); + // FontRole data reads the DeckList role live; rebroadcast so attached + // views re-query instead of waiting for an incidental repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + emit layoutAboutToBeChanged(); + emit layoutChanged(); + }); } FilterTreeModel::~FilterTreeModel() @@ -202,7 +210,8 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::FontRole: if (!node->isLeaf()) { - QFont f; + // Family comes from the DeckList theme role; bold stays caller-side. + QFont f = themeManager->appFont(AppFont::DeckList); f.setBold(true); return f; } diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp index 0e8b6ced3..a688cafcf 100644 --- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp @@ -3,6 +3,7 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/card_localization.h" #include "../../interface/card_picture_loader/card_picture_loader.h" +#include "../../interface/theme_manager.h" #include "../card_dimensions.h" #include "../game_scene.h" #include "../z_values.h" @@ -34,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -143,8 +143,8 @@ QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle) { - const int MAX_FONT_SIZE = SettingsCache::instance().appearance().getMaxFontSize(); - const int fontSize = std::max(9, MAX_FONT_SIZE); + const int minSize = std::max(1, themeManager->cardTextMinSize()); + const int fontSize = std::max(minSize, std::max(1, themeManager->cardTextMaxSize())); QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect()); @@ -154,7 +154,7 @@ void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &transla painter->rotate(angle); painter->translate(-translatedSize.width() / 2, -translatedSize.height() / 2); - QFont f; + QFont f = themeManager->appFont(AppFont::CardText); f.setPixelSize(fontSize * scale); painter->setFont(f); diff --git a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp index 50e80d359..3a740aa98 100644 --- a/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp +++ b/cockatrice/src/game_graphics/board/abstract_graphics_item.cpp @@ -1,5 +1,7 @@ #include "abstract_graphics_item.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -20,7 +22,7 @@ void AbstractGraphicsItem::paintNumberEllipse(int number, painter->save(); QString numStr = QString::number(number); - QFont font("Serif"); + QFont font = themeManager->cardTextFont(); font.setPixelSize(fontSize); font.setWeight(QFont::Bold); diff --git a/cockatrice/src/game_graphics/board/counter_general.cpp b/cockatrice/src/game_graphics/board/counter_general.cpp index 4a8bbd6a4..23618e6bf 100644 --- a/cockatrice/src/game_graphics/board/counter_general.cpp +++ b/cockatrice/src/game_graphics/board/counter_general.cpp @@ -1,6 +1,7 @@ #include "counter_general.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "abstract_graphics_item.h" #include @@ -41,7 +42,7 @@ void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * / painter->drawPixmap(QPoint(0, 0), pixmap); if (value) { - QFont f("Serif"); + QFont f = themeManager->cardTextFont(); f.setPixelSize(qMax((int)(radius * scaleFactor), 10)); f.setWeight(QFont::Bold); painter->setPen(Qt::black); diff --git a/cockatrice/src/game_graphics/deckview/deck_view.cpp b/cockatrice/src/game_graphics/deckview/deck_view.cpp index 6e0b0eaaa..f62d33335 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view.cpp @@ -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> 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() diff --git a/cockatrice/src/game_graphics/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp index db1040006..1f780b795 100644 --- a/cockatrice/src/game_graphics/game_scene.cpp +++ b/cockatrice/src/game_graphics/game_scene.cpp @@ -1,6 +1,7 @@ #include "game_scene.h" #include "../client/settings/cache_settings.h" +#include "../interface/theme_manager.h" #include "animated_item.h" #include "board/abstract_card_item.h" #include "board/arrow_item.h" @@ -64,6 +65,14 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) addItem(phasesToolbar); connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::minPlayersForMultiColumnLayoutChanged, this, &GameScene::rearrange); + // CardText items (P/T boxes, counters, player names) read the role at + // paint time and cache their rendering, so touch every item to invalidate + // the caches and pick up the new family on font changes. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + for (auto *item : items()) { + item->update(); + } + }); rearrange(); } diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index cb59b3d7d..7a51fc97f 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -2,12 +2,14 @@ #include "../client/settings/cache_settings.h" #include "../client/settings/shortcuts_settings.h" +#include "../interface/theme_manager.h" #include "game_scene.h" #include #include #include #include +#include #include #include #include @@ -24,6 +26,21 @@ #include #include +namespace +{ +QFont deckOverlayFont(int pixelSize, bool bold) +{ + // The drag/total counters and the tally columns are in-game overlays, not + // deck list text: keep the monospace family the stylesheets used to set so + // digit widths stay stable as counts tick and `white-space: pre` lines up. + QFont font(QStringLiteral("monospace")); + font.setStyleHint(QFont::Monospace); + font.setPixelSize(pixelSize); + font.setBold(bold); + return font; +} +} // namespace + // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // This subclass disables that behavior so dragCountLabel can appear above it. class SelectionRubberBand : public QRubberBand @@ -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,27 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par tallyLayout->setContentsMargins(2, 2, 2, 2); tallyLayout->setSpacing(2); tallyContainer->hide(); + + applyDeckOverlayFonts(); +} + +void GameView::applyDeckOverlayFonts() +{ + dragCountLabel->setFont(deckOverlayFont(14, true)); + totalCountLabel->setFont(deckOverlayFont(16, true)); + tallyContainer->setFont(deckOverlayFont(12, false)); + for (int i = 0; i < tallyLayout->count(); ++i) { + if (auto *label = qobject_cast(tallyLayout->itemAt(i)->widget())) { + // 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 +256,8 @@ QSize GameView::rebuildTallyLabels(const QList &entries) { clearTallyLabels(); - const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;"); - const QString countStyle = - QStringLiteral("color: white; font-size: 14px; font-weight: bold; background: transparent;"); + const QString nameStyle = QStringLiteral("color: white; background: transparent;"); + const QString countStyle = QStringLiteral("color: white; background: transparent;"); int totalHeight = 0; int maxNameWidth = 0; @@ -231,11 +267,13 @@ QSize GameView::rebuildTallyLabels(const QList &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); diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index e57267734..99a73a0a8 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -50,6 +50,7 @@ private slots: void refreshShortcuts(); void updateTotalSelectionCount(const QSize &viewSize = QSize()); void setFocusDisabled(bool disabled); + void applyDeckOverlayFonts(); public slots: void updateSceneRect(const QRectF &rect); diff --git a/cockatrice/src/game_graphics/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp index 1cd97e7b5..0d10ba4ab 100644 --- a/cockatrice/src/game_graphics/player/player_target.cpp +++ b/cockatrice/src/game_graphics/player/player_target.cpp @@ -2,6 +2,7 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "../board/abstract_counter.h" #include "../board/abstract_graphics_item.h" #include "../board/arrow_target.h" @@ -76,7 +77,7 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* QRectF translatedRect = path.controlPointRect(); QSize translatedSize = translatedRect.size().toSize(); - QFont font("Serif"); + QFont font = themeManager->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); diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index d413a6c04..daa5dc98a 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -1,5 +1,6 @@ #include "deck_loader.h" +#include "../theme_manager.h" #include "libcockatrice/card/card_info.h" #include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/deck_list/tree/abstract_deck_list_card_node.h" @@ -579,6 +580,8 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, const InnerDecklistNode if (!cards.isEmpty()) { QTextBlockFormat blockFormat; QTextCharFormat charFormat; + // Family comes from the DeckList theme role; sizes/weights unchanged. + charFormat.setFontFamilies({themeManager->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); diff --git a/cockatrice/src/interface/palette_editor/color_button.cpp b/cockatrice/src/interface/palette_editor/color_button.cpp index a7db23d31..ae9bd6297 100644 --- a/cockatrice/src/interface/palette_editor/color_button.cpp +++ b/cockatrice/src/interface/palette_editor/color_button.cpp @@ -1,5 +1,7 @@ #include "color_button.h" +#include "../theme_manager.h" + #include #include #include @@ -21,6 +23,7 @@ ColorButton::ColorButton(QWidget *parent) : QToolButton(parent) setCursor(Qt::PointingHandCursor); setToolTip(tr("Click to pick a color")); connect(this, &QToolButton::clicked, this, &ColorButton::pickColor); + connect(themeManager, &ThemeManager::fontsChanged, this, &ColorButton::updateSwatch); } void ColorButton::setColor(const QColor &c) @@ -73,7 +76,8 @@ void ColorButton::updateSwatch() // Hex label — black or white for contrast int luma = 0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue(); painter.setPen((luma > 128 && color.alpha() > 80) ? QColor(0, 0, 0, 180) : QColor(255, 255, 255, 200)); - QFont f = font(); + // Family comes from the InterfaceDefault theme role; the 8px size stays caller-side. + QFont f = themeManager->appFont(AppFont::InterfaceDefault); f.setPixelSize(8); painter.setFont(f); painter.drawText(rect(), Qt::AlignCenter, color.name().toUpper()); diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.cpp b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp new file mode 100644 index 000000000..c4b81de86 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.cpp @@ -0,0 +1,208 @@ +#include "font_grid_widget.h" + +#include "../theme_manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr int MIN_FONT_SIZE = 6; +static constexpr int MAX_FONT_SIZE = 72; +static constexpr int CARD_TEXT_MIN_SIZE = 1; +static constexpr int CARD_TEXT_MAX_SIZE = 12; +static constexpr int FALLBACK_FONT_SIZE = 10; + +static const QMap APP_FONT_DESCRIPTIONS = { + {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(); + for (int i = 0; i < fontEnum.keyCount(); ++i) { + auto role = static_cast(fontEnum.value(i)); + + auto *label = new QLabel(this); + grid->addWidget(label, i, 0); + + auto *familyCombo = new QFontComboBox(this); + grid->addWidget(familyCombo, i, 1); + connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); }); + + 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(); + for (int i = 0; i < fontEnum.keyCount(); ++i) { + auto role = static_cast(fontEnum.value(i)); + const FontRow &row = fontRows[role]; + + // Roles absent from the file resolve the same way the runtime does: an unset + // role falls back to the config's InterfaceDefault, and only then to the + // startup font. appFont() would return the live preview instead, so Reset and + // a first load must not measure against it. + const QFont fallback = cfg.fonts.value(AppFont::InterfaceDefault, themeManager->defaultAppFont(role)); + const QFont font = cfg.fonts.value(role, fallback); + row.familyCombo->setCurrentFont(font); + + if (role == AppFont::CardText && row.minSpin != nullptr) { + // A previous load may have narrowed the linked bounds (min 20 pulls + // max up to 20), which would clamp the values written below. Reset + // both to the full range first; the valueChanged lambdas re-link + // them as the values are set. + row.minSpin->setRange(CARD_TEXT_MIN_SIZE, MAX_FONT_SIZE); + row.sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE); + + // The max spin is a size-only override. Without CardTextMaxSize it + // follows the CardText family size, and without CardText at all it + // must match cardTextMaxSize()'s default rather than the system + // startup size, which currentFontConfig() would otherwise bake in. + const int fallbackMax = cfg.fonts.contains(role) ? font.pointSize() : CARD_TEXT_MAX_SIZE; + int maxSize = cfg.cardTextMaxSize > 0 ? cfg.cardTextMaxSize : fallbackMax; + if (maxSize <= 0) { + maxSize = CARD_TEXT_MAX_SIZE; + } + int minSize = cfg.cardTextMinSize > 0 ? cfg.cardTextMinSize : themeManager->cardTextMinSize(); + minSize = qBound(CARD_TEXT_MIN_SIZE, minSize, MAX_FONT_SIZE); + maxSize = qBound(MIN_FONT_SIZE, maxSize, MAX_FONT_SIZE); + if (maxSize < minSize) { + maxSize = minSize; + } + + // Max first, then min: each value re-applies the linked bound, so + // the pair ends up consistent instead of clamping the smaller one. + row.sizeSpin->setValue(maxSize); + row.minSpin->setValue(minSize); + continue; + } + + int size = font.pointSize(); + if (size <= 0) { + size = qApp->font().pointSize(); + } + if (size <= 0) { + size = FALLBACK_FONT_SIZE; + } + row.sizeSpin->setValue(qBound(MIN_FONT_SIZE, size, MAX_FONT_SIZE)); + } + blockSignals(false); +} + +FontConfig FontGridWidget::currentFontConfig() const +{ + FontConfig cfg; + QMetaEnum fontEnum = QMetaEnum::fromType(); + for (int i = 0; i < fontEnum.keyCount(); ++i) { + auto role = static_cast(fontEnum.value(i)); + const FontRow &row = fontRows.value(role); + + // Canonical family + point size only: a fresh QFont carries default + // (non-bold, non-italic) weight/style, so nothing combo-side leaks in. + QFont font(row.familyCombo->currentFont().family()); + font.setPointSize(row.sizeSpin->value()); + cfg.fonts[role] = font; + + if (role == AppFont::CardText && row.minSpin != nullptr) { + cfg.cardTextMinSize = row.minSpin->value(); + cfg.cardTextMaxSize = row.sizeSpin->value(); + } + } + return cfg; +} + +void FontGridWidget::retranslateUi() +{ + QMetaEnum fontEnum = QMetaEnum::fromType(); + for (int i = 0; i < fontEnum.keyCount(); ++i) { + auto role = static_cast(fontEnum.value(i)); + const FontRow &row = fontRows.value(role); + + row.label->setText(roleDisplayName(role)); + row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); + row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, ""))); + row.familyCombo->setAccessibleName(roleDisplayName(role)); + if (role == AppFont::CardText && row.minSpin != nullptr) { + row.minSpin->setToolTip(tr("Minimum card text size")); + row.minSpin->setAccessibleName(tr("Minimum card text size")); + row.minSpin->setSuffix(tr(" pt")); + row.sizeSpin->setToolTip(tr("Maximum card text size")); + row.sizeSpin->setAccessibleName(tr("Maximum card text size")); + row.sizeSpin->setSuffix(tr(" pt")); + continue; + } + row.sizeSpin->setToolTip(tr("Point size for %1").arg(roleDisplayName(role))); + row.sizeSpin->setAccessibleName(tr("Point size for %1").arg(roleDisplayName(role))); + row.sizeSpin->setSuffix(tr(" pt")); + } +} diff --git a/cockatrice/src/interface/palette_editor/font_grid_widget.h b/cockatrice/src/interface/palette_editor/font_grid_widget.h new file mode 100644 index 000000000..07ccfa868 --- /dev/null +++ b/cockatrice/src/interface/palette_editor/font_grid_widget.h @@ -0,0 +1,44 @@ +#ifndef COCKATRICE_FONT_GRID_WIDGET_H +#define COCKATRICE_FONT_GRID_WIDGET_H + +#include "../theme_config.h" + +#include +#include +#include + +class QFontComboBox; +class QGridLayout; +class QLabel; +class QSpinBox; + +// Per-role font editors — one (family, point size) row per AppFont::Role. +// Owns the load/read round-trip for FontConfig but has no file I/O, mirroring +// PaletteGridWidget. Only family and point size are edited; weight and italic +// stay caller-side concerns and are stripped when reading back. +class FontGridWidget : public QWidget +{ + Q_OBJECT +public: + explicit FontGridWidget(QWidget *parent = nullptr); + + void loadFonts(const FontConfig &cfg); + FontConfig currentFontConfig() const; + void retranslateUi(); + +signals: + void fontsChanged(); + +private: + struct FontRow + { + QLabel *label = nullptr; + QFontComboBox *familyCombo = nullptr; + QSpinBox *sizeSpin = nullptr; + QSpinBox *minSpin = nullptr; // CardText only: shrink-to-fit floor + }; + + QMap fontRows; +}; + +#endif // COCKATRICE_FONT_GRID_WIDGET_H diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index b6271291d..5bcfbb251 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -2,6 +2,7 @@ #include "../theme_config.h" #include "../theme_manager.h" +#include "font_grid_widget.h" #include "palette_generator.h" #include "palette_grid_widget.h" #include "quick_setup_panel.h" @@ -41,6 +42,7 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt // Load both scheme configs upfront so switching is instant loadSchemes(); + loadFonts(); loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light"; @@ -125,6 +127,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; @@ -157,6 +186,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); @@ -166,15 +199,32 @@ void PaletteEditorDialog::setupUi() paletteGridToggleButton->setText(open ? tr("▼ Edit Palette") : tr("▶ Edit Palette")); paletteGridSeparator->setVisible(open); paletteGrid->setVisible(open); - - if (open) { - setMinimumHeight(680); - resize(width(), 680); - } else { - setMinimumHeight(220); - adjustSize(); // shrinks to fit just the visible content - } + updateAdvancedHeight(); }); + connect(fontToggleButton, &QPushButton::toggled, this, [this](bool open) { + fontToggleButton->setText(open ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts")); + fontSeparator->setVisible(open); + fontSection->setVisible(open); + updateAdvancedHeight(); + }); +} + +void PaletteEditorDialog::updateAdvancedHeight() +{ + int height = 220; + if (paletteGridToggleButton->isChecked()) { + height += 460; + } + if (fontToggleButton->isChecked()) { + height += 200; + } + setMinimumHeight(height); + + if (paletteGrid->isVisible() || fontSection->isVisible()) { + resize(width(), height); + } else { + adjustSize(); // shrinks to fit just the visible content + } } void PaletteEditorDialog::retranslateUi() @@ -189,12 +239,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); @@ -228,6 +282,13 @@ void PaletteEditorDialog::loadSchemes() } } +void PaletteEditorDialog::loadFonts() +{ + savedFontConfig = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName); + fontGrid->loadFonts(savedFontConfig); + fontsDirty = false; +} + void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme) { QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight); @@ -258,6 +319,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() @@ -308,6 +372,20 @@ 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(); } @@ -316,6 +394,12 @@ void PaletteEditorDialog::onReset() { workingConfig[loadedScheme] = savedConfig[loadedScheme]; paletteGrid->loadPalette(savedConfig[loadedScheme]); + // Restore the live theme before re-reading the grid: loadFonts resolves + // unset roles, and the preview has currentFonts pointing at the edited + // config until the theme is reloaded. + themeManager->reloadCurrentTheme(); + fontGrid->loadFonts(savedFontConfig); + fontsDirty = false; seedAccentFromScheme(loadedScheme); } @@ -350,6 +434,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) diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h index e29a70cb0..5e47536b5 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.h +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.h @@ -15,6 +15,7 @@ class QComboBox; class QDialogButtonBox; class QPushButton; class PaletteGridWidget; +class FontGridWidget; class QuickSetupPanel; class QFrame; class QWidget; @@ -25,6 +26,7 @@ class PaletteEditorDialog : public QDialog public: explicit PaletteEditorDialog(const QString &themeDirPath, const QString &themeName, QWidget *parent = nullptr); void loadSchemes(); + void reject() override; private slots: void onSave(); @@ -38,8 +40,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 +53,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 +77,18 @@ private: QMap workingConfig; QMap savedConfig; + // Fonts are shared per theme (fonts.toml, not split by light/dark), so a + // single saved config suffices; the live edits are read from fontGrid. + FontConfig savedFontConfig; + // 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 \ No newline at end of file +#endif // COCKATRICE_PALETTE_EDITOR_DIALOG_H diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp index 463c4038d..392f01d0d 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.cpp @@ -76,6 +76,18 @@ PaletteGridWidget::PaletteGridWidget(QWidget *parent) : QWidget(parent) layout = QtUtils::makeLayout(this); layout->addWidget(scroll); + + connect(themeManager, &ThemeManager::fontsChanged, this, &PaletteGridWidget::applyInterfaceDefaultFont); +} + +void PaletteGridWidget::applyInterfaceDefaultFont() +{ + // The editor's own chrome follows the InterfaceDefault theme role; bold stays caller-side. + for (auto *label : headerLabels) { + QFont headerFont = themeManager->appFont(AppFont::InterfaceDefault); + headerFont.setBold(true); + label->setFont(headerFont); + } } void PaletteGridWidget::buildGrid(QWidget *host) @@ -101,7 +113,8 @@ void PaletteGridWidget::buildGrid(QWidget *host) auto *label = new QLabel(groupHeaders[col], host); label->setAlignment(Qt::AlignCenter); label->setToolTip(groupTips[col]); - label->setFont(QtUtils::boldFont(label->font())); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + label->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); label->setAutoFillBackground(true); label->setContentsMargins(4, 4, 4, 4); grid->addWidget(label, 0, col + 1); @@ -146,7 +159,7 @@ void PaletteGridWidget::buildGrid(QWidget *host) auto *appHeader = new QLabel(tr("App colors"), host); appHeader->setToolTip(tr("Application-specific colors layered on top of the Qt palette")); - appHeader->setFont(QtUtils::boldFont(appHeader->font())); + appHeader->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); appHeader->setAutoFillBackground(true); appHeader->setContentsMargins(4, 4, 4, 4); grid->addWidget(appHeader, appHeaderRow, 0, 1, 4); diff --git a/cockatrice/src/interface/palette_editor/palette_grid_widget.h b/cockatrice/src/interface/palette_editor/palette_grid_widget.h index f1ff41ec2..7c527a064 100644 --- a/cockatrice/src/interface/palette_editor/palette_grid_widget.h +++ b/cockatrice/src/interface/palette_editor/palette_grid_widget.h @@ -34,6 +34,10 @@ private: void changeEvent(QEvent *e); void refreshChromePalettes(); +private slots: + void applyInterfaceDefaultFont(); + +private: QMap> colorButtons; QMap appColorButtons; QScrollArea *scroll; diff --git a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp index 04f98dcc3..00eac3253 100644 --- a/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp +++ b/cockatrice/src/interface/palette_editor/quick_setup_panel.cpp @@ -1,5 +1,6 @@ #include "quick_setup_panel.h" +#include "../theme_manager.h" #include "color_button.h" #include @@ -28,10 +29,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); diff --git a/cockatrice/src/interface/theme_config.cpp b/cockatrice/src/interface/theme_config.cpp index 652e322c8..a75330db4 100644 --- a/cockatrice/src/interface/theme_config.cpp +++ b/cockatrice/src/interface/theme_config.cpp @@ -304,4 +304,183 @@ QPalette PaletteConfig::apply(QPalette base) const } return base; -} \ No newline at end of file +} + +bool FontConfig::hasFonts() const +{ + return !fonts.isEmpty() || cardTextMinSize != 1 || cardTextMaxSize > 0; +} + +QString FontConfig::toToml() const +{ + QMetaEnum roleEnum = QMetaEnum::fromType(); + + QString entries; + + for (auto it = fonts.cbegin(); it != fonts.cend(); ++it) { + const char *roleName = roleEnum.valueToKey(it.key()); + + if (!roleName) { + continue; + } + + QString family = it.value().family().trimmed(); + int pointSize = it.value().pointSize(); + + if (family.isEmpty() || pointSize <= 0) { + continue; + } + + entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize); + } + + if (cardTextMinSize != 1) { + entries += QString("%1 = %2\n").arg(QStringLiteral("CardTextMinSize"), -20).arg(cardTextMinSize); + } + + if (cardTextMaxSize > 0) { + entries += QString("%1 = %2\n").arg(QStringLiteral("CardTextMaxSize"), -20).arg(cardTextMaxSize); + } + + if (entries.isEmpty()) { + return {}; + } + + return "[Fonts]\n" + entries + "\n"; +} + +QString FontConfig::fileName() +{ + return "fonts.toml"; +} + +FontConfig FontConfig::fromFile(const QString &filePath) +{ + FontConfig cfg; + + QFile f(filePath); + + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + return cfg; + } + + QMetaEnum roleEnum = QMetaEnum::fromType(); + + QString currentSection; + + QTextStream in(&f); + + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + + if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) { + continue; + } + + if (line.startsWith('[') && line.endsWith(']')) { + currentSection = line.mid(1, line.length() - 2).trimmed(); + continue; + } + + if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) { + continue; + } + + int eq = line.indexOf('='); + + if (eq < 0) { + continue; + } + + QString key = line.left(eq).trimmed(); + QString value = line.mid(eq + 1).trimmed(); + + // Strip inline comments if preceded by whitespace + for (int i = 1; i < value.size(); ++i) { + if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) { + value = value.left(i).trimmed(); + break; + } + } + + // Strip surrounding quotes, if any + if (value.size() >= 2 && + ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) { + value = value.mid(1, value.size() - 2).trimmed(); + } + + // CardText size bounds: bare integers, e.g. CardTextMinSize = 2. + // Tolerate quoting like the family entries; skip invalid lines. + QString sizeKey = key; + if (sizeKey.startsWith("AppFont::")) { + sizeKey = sizeKey.mid(QStringLiteral("AppFont::").size()); + } + if (sizeKey.compare("CardTextMinSize", Qt::CaseInsensitive) == 0) { + bool ok = false; + int minSize = value.trimmed().toInt(&ok); + if (!ok || minSize <= 0) { + continue; + } + cfg.cardTextMinSize = minSize; + continue; + } + if (sizeKey.compare("CardTextMaxSize", Qt::CaseInsensitive) == 0) { + bool ok = false; + int maxSize = value.trimmed().toInt(&ok); + if (!ok || maxSize <= 0) { + continue; + } + cfg.cardTextMaxSize = maxSize; + continue; + } + + int comma = value.lastIndexOf(','); + + if (comma < 0) { + continue; + } + + QString family = value.left(comma).trimmed(); + + // Tolerate a partially-quoted family, e.g. "Inter", 10 + if (family.size() >= 2 && + ((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) { + family = family.mid(1, family.size() - 2).trimmed(); + } + + bool ok = false; + int pointSize = value.mid(comma + 1).trimmed().toInt(&ok); + + if (!ok || family.isEmpty() || pointSize <= 0) { + continue; + } + + if (key.startsWith("AppFont::")) { + key = key.mid(QStringLiteral("AppFont::").size()); + } + + int roleInt = roleEnum.keyToValue(key.toUtf8().constData()); + + if (roleInt < 0) { + continue; + } + + // Canonical family + point size only: a fresh QFont carries default + // (non-bold, non-italic) weight/style, so nothing caller-side leaks in. + // No validation against installed fonts; Qt substitution applies. + QFont font(family); + font.setPointSize(pointSize); + cfg.fonts[static_cast(roleInt)] = font; + } + + return cfg; +} + +FontConfig FontConfig::fromThemeDir(const QString &themeDirPath) +{ + if (themeDirPath.isEmpty()) { + return {}; + } + + return fromFile(QDir(themeDirPath).absoluteFilePath(fileName())); +} diff --git a/cockatrice/src/interface/theme_config.h b/cockatrice/src/interface/theme_config.h index 8b91613f5..ebad7ef46 100644 --- a/cockatrice/src/interface/theme_config.h +++ b/cockatrice/src/interface/theme_config.h @@ -2,6 +2,7 @@ #define COCKATRICE_THEME_CONFIG_H #include +#include #include #include #include @@ -23,6 +24,23 @@ enum Role Q_ENUM_NS(Role) } // namespace AppColor +// Application-specific font roles, one configurable family + point size per +// purpose. Stored in a shared fonts.toml under a [Fonts] section (one file per +// theme, not split by light/dark). Only family and point size are stored; +// weight/italic are stripped on load and stay caller-side concerns. +namespace AppFont +{ +Q_NAMESPACE +enum Role +{ + InterfaceDefault, + ChatLog, + CardText, + DeckList, +}; +Q_ENUM_NS(Role) +} // namespace AppFont + struct ThemeConfig { QString colorScheme; @@ -60,4 +78,41 @@ struct PaletteConfig QPalette apply(QPalette base) const; }; -#endif // COCKATRICE_THEME_CONFIG_H \ No newline at end of file +/** @brief Per-purpose font configuration, stored as family + point size only. + * + * Serialized to a shared fonts.toml file with a [Fonts] section, e.g. + * @code + * [Fonts] + * InterfaceDefault = "Inter, 10" + * CardText = "Noto Serif, 12" + * CardTextMinSize = 1 + * @endcode + * CardText stores family + MAX size; cardTextMinSize stores the overlay + * shrink-to-fit floor (omitted from TOML when 1). + */ +struct FontConfig +{ + QMap fonts; + int cardTextMinSize = 1; + /// Optional size-only maximum for CardText, used by the retired maxFontSize + /// migration so seeding it does not have to pin a family. + int cardTextMaxSize = 0; + + bool operator==(const FontConfig &rhs) const + { + return fonts == rhs.fonts && cardTextMinSize == rhs.cardTextMinSize && cardTextMaxSize == rhs.cardTextMaxSize; + } + bool operator!=(const FontConfig &rhs) const + { + return !(*this == rhs); + } + bool hasFonts() const; + QString toToml() const; + + static QString fileName(); + + static FontConfig fromFile(const QString &filePath); + static FontConfig fromThemeDir(const QString &themeDirPath); +}; + +#endif // COCKATRICE_THEME_CONFIG_H diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 893cdc795..e23284393 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -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] { @@ -440,6 +443,46 @@ PaletteConfig ThemeManager::loadDefaultPaletteConfig(const QString &themeDirPath return cfg; } +FontConfig ThemeManager::loadFontConfig(const QString &themeDirPath) +{ + if (themeDirPath.isEmpty()) { + return {}; + } + 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()) { + return false; + } + + QDir dir(themeDirPath); + if (!dir.exists()) { + dir.mkpath("."); + } + + QFile f(dir.absoluteFilePath(FontConfig::fileName())); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + return false; + } + + QTextStream(&f) << cfg.toToml(); + return true; +} + bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg) { if (!savePaletteConfig(themeDirPath, colorScheme, cfg)) { @@ -488,6 +531,24 @@ void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &schem applyStyleAndPalette(themeName, themeCfg, cfg, scheme); } +void ThemeManager::previewFonts(const FontConfig &cfg) +{ + currentFonts = cfg; + + // QApplication::setFont already sends a font change to every widget, so a + // repolish pass is not needed for fonts (unlike the palette path). + const auto it = cfg.fonts.constFind(AppFont::InterfaceDefault); + if (it != cfg.fonts.constEnd()) { + qApp->setFont(it.value()); + } else { + // A config without InterfaceDefault means "no override": restore the + // startup font instead of leaving the last preview applied. + qApp->setFont(defaultFont); + } + + emit fontsChanged(); +} + void ThemeManager::applyStyleAndPalette(const QString &themeName, const ThemeConfig &themeCfg, const PaletteConfig &palCfg, @@ -591,6 +652,80 @@ QColor ThemeManager::appColor(AppColor::Role role) const return accent; } +QFont ThemeManager::appFont(AppFont::Role role) const +{ + const auto it = currentFonts.fonts.constFind(role); + if (it != currentFonts.fonts.constEnd()) { + return it.value(); + } + + // Role fallbacks preserve the pre-theme defaults when no fonts.toml is + // present: an unset role resolves to the application font. Surfaces that + // hardcoded a different family ask for it explicitly: counter painters + // through cardTextFont(), the deck view headers and printed deck list + // through deckListFont(), the report dialogs through + // report_utils::reportLogFont(). Only the family basis is fixed there; + // sizes stay caller-side. + return qApp->font(); +} + +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 +{ + if (currentFonts.cardTextMaxSize > 0) { + return currentFonts.cardTextMaxSize; + } + const auto it = currentFonts.fonts.constFind(AppFont::CardText); + if (it != currentFonts.fonts.constEnd()) { + const int size = it.value().pointSize(); + if (size > 0) { + return size; + } + } + return 12; +} + +int ThemeManager::cardTextMinSize() const +{ + if (currentFonts.cardTextMinSize > 0) { + return currentFonts.cardTextMinSize; + } + return 1; +} + void ThemeManager::themeChangedSlot() { QString themeName = SettingsCache::instance().getThemeName(); @@ -642,6 +777,19 @@ void ThemeManager::themeChangedSlot() applyStyleAndPalette(themeName, themeCfg, palette, activeScheme); + // ── Load fonts: shared per theme, no light/dark split ─────────────────── + 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(); + QStringList resources; if (!dirPath.isEmpty()) { resources << dir.absolutePath(); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index c1ab65685..0cd44125e 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -47,10 +48,15 @@ private: // palette is applied. Used as the base when a theme supplies no palette, so // switching away from a custom palette restores the original colours. QPalette defaultPalette; + // Pristine application font captured at startup, before any theme font is + // applied. Restored when a theme (or a preview) supplies no InterfaceDefault + // font, so switching away from a themed font cannot leave it behind. + QFont defaultFont; QString currentThemePath; std::array brushes; QStringMap availableThemes; QMap currentAppColors; + FontConfig currentFonts; /* Internal cache for multiple backgrounds */ @@ -105,6 +111,9 @@ public: // Load/save per-scheme palette colors static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme); static bool savePaletteConfig(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg); + // Load/save shared per-theme fonts (fonts.toml, not split by scheme) + static FontConfig loadFontConfig(const QString &themeDirPath); + static bool saveFontConfig(const QString &themeDirPath, const FontConfig &cfg); // Resolve prefix to a scheme-qualified "theme:" path. Existence is probed // internally across the formats themes may ship (.png/.jpg/.svg), so // callers load the returned path directly. Prefers "-" @@ -123,6 +132,9 @@ public: // theme directory when it is absent from the resolved (user) directory. static PaletteConfig loadDefaultPaletteConfig(const QString &themeDirPath, const QString &themeName, const QString &colorScheme); + // Load the theme's shipped default fonts, falling back to the system theme + // directory when fonts.toml is absent from the resolved (user) directory. + static FontConfig loadDefaultFontConfig(const QString &themeDirPath, const QString &themeName); /** @brief Writes cfg to disk as the theme's palette-.toml and updates the * theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave * and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */ @@ -132,10 +144,36 @@ public: void reloadCurrentTheme(); void previewPalette(const PaletteConfig &cfg, const QString &scheme); + void previewFonts(const FontConfig &cfg); // Resolves an application color role: the theme's stored [AppColors] value // when present, otherwise a palette-accent-derived fallback. QColor appColor(AppColor::Role role) const; + /** @brief Resolves an application font role: the theme's stored [Fonts] + * value when present, otherwise the application font. 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 CardTextMaxSize, else the CardText + * point size, else 12. */ + int cardTextMaxSize() const; + /** @brief CardText shrink-to-fit floor: stored CardTextMinSize, else 1. */ + int cardTextMinSize() const; QBrush &getBgBrush(Role zone); QBrush getExtraBgBrush(Role zone, int zoneId = 0); @@ -144,6 +182,7 @@ protected slots: signals: void themeChanged(); void paletteChanged(); + void fontsChanged(); }; extern ThemeManager *themeManager; diff --git a/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp b/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp index c4b347047..8475a0ed9 100644 --- a/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp +++ b/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp @@ -1,5 +1,6 @@ #include "art_crop_attribution.h" +#include "../../theme_manager.h" #include "libcockatrice/card/printing/printing_info.h" #include @@ -35,7 +36,10 @@ QRectF paintArtAttribution(QPainter &painter, painter.save(); + // Family comes from the CardText theme role; the incoming painter size + // stays the basis for the scale factor exactly as before. QFont font = painter.font(); + font.setFamily(themeManager->appFont(AppFont::CardText).family()); font.setPointSizeF(qMax(6.0, font.pointSizeF() * scale)); painter.setFont(font); diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index 04fe9ab69..4111a9cec 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -1,5 +1,7 @@ #include "card_info_picture_with_text_overlay_widget.h" +#include "../../theme_manager.h" + #include #include #include @@ -43,6 +45,7 @@ CardInfoPictureWithTextOverlayWidget::CardInfoPictureWithTextOverlayWidget(QWidg fontSize(fontSize), textAlignment(alignment), highlighted(false) { this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + connect(themeManager, &ThemeManager::fontsChanged, this, &CardInfoPictureWithTextOverlayWidget::applyCardTextFont); } /** @@ -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 diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h index 6c547dc13..c4a967498 100644 --- a/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -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; }; diff --git a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp index 85dba6468..df2a645f1 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/resizable_panel.cpp @@ -1,5 +1,6 @@ #include "resizable_panel.h" +#include "../interface/theme_manager.h" #include "abstract_analytics_panel_widget.h" #include "libcockatrice/utility/qt_utils.h" @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -93,9 +95,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); }") diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp index 1e1aefe58..a9c9cad7a 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.cpp @@ -1,5 +1,7 @@ #include "deck_list_style_proxy.h" +#include "../../theme_manager.h" + #include #include #include @@ -8,6 +10,16 @@ #include #include +DeckListStyleProxy::DeckListStyleProxy(QObject *parent) : QIdentityProxyModel(parent) +{ + // FontRole data reads the DeckList role live; rebroadcast so attached + // views re-query instead of waiting for an incidental repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + emit layoutAboutToBeChanged(); + emit layoutChanged(); + }); +} + QVariant DeckListStyleProxy::data(const QModelIndex &index, int role) const { QModelIndex src = mapToSource(index); @@ -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; } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h index e4d1de4f4..3149da6de 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_list_style_proxy.h @@ -8,7 +8,7 @@ class DeckListStyleProxy : public QIdentityProxyModel { Q_OBJECT public: - using QIdentityProxyModel::QIdentityProxyModel; + explicit DeckListStyleProxy(QObject *parent = nullptr); [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; }; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp index 2a2758ba5..a7387a068 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp @@ -1,11 +1,13 @@ #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" #include "libcockatrice/protocol/pb/serverinfo_report.pb.h" #include +#include #include #include #include @@ -24,7 +26,6 @@ #include #include #include -#include #include #include @@ -69,7 +70,8 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent) auto *chatGroup = new QGroupBox(tr("Chat Log Context")); chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - chatLogEdit->setFont(QtUtils::monospaceFont()); + applyChatLogFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &DlgMyReports::applyChatLogFont); auto *chatLayout = new QVBoxLayout(chatGroup); chatLayout->setContentsMargins(4, 4, 4, 4); chatLayout->addWidget(chatLogEdit); @@ -122,6 +124,11 @@ DlgMyReports::DlgMyReports(AbstractClient *_client, QWidget *parent) refreshList(); } +void DlgMyReports::applyChatLogFont() +{ + chatLogEdit->setFont(report_utils::reportLogFont()); +} + void DlgMyReports::refreshList() { selectedReportIdBeforeRefresh = selectedReportId; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h index dc9f90d2d..90772f051 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.h @@ -29,6 +29,7 @@ private slots: void reportDetailsResponse(const Response &response); void addComment(); void addCommentResponse(const Response &response); + void applyChatLogFont(); private: void loadReportDetails(const ServerInfo_Report &report); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp index 3ef45020b..c7c32469b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.cpp @@ -1,10 +1,13 @@ #include "dlg_report_user.h" +#include "../interface/theme_manager.h" +#include "../utility/report_utils.h" #include "abstract_client.h" #include "libcockatrice/protocol/pb/response.pb.h" #include #include +#include #include #include #include @@ -91,7 +94,8 @@ DlgReportUser::DlgReportUser(AbstractClient *_client, auto *chatLayout = new QVBoxLayout(chatGroup); chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - chatLogEdit->setFont(QtUtils::monospaceFont()); + applyChatLogFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &DlgReportUser::applyChatLogFont); if (!_autoChatLog.isEmpty()) { chatLogEdit->setPlainText(_autoChatLog); } else { @@ -120,6 +124,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(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h index c7fce055d..0f2fa5080 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_report_user.h @@ -27,6 +27,7 @@ public: private slots: void actSubmit(); void reportResponse(const Response &response); + void applyChatLogFont(); private: static QString categoryLabel(const QString &key); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp index d29aee27f..49bacd081 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_shared_decks_preview.cpp @@ -2,6 +2,7 @@ #include "../deck_share/shared_deck_preview_widget.h" #include "../general/layout_containers/flow_widget.h" +#include "../interface/theme_manager.h" #include #include @@ -35,9 +36,14 @@ DlgSharedDecksPreview::DlgSharedDecksPreview(QWidget *parent, // QLabels render AutoText and markup would otherwise be shown as rich text. auto *titleLabel = new QLabel(tr("Share: %1").arg((shareName.isEmpty() ? tr("Untitled") : shareName).toHtmlEscaped()), this); - QFont titleFont = QtUtils::boldFont(titleLabel->font()); - 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 = QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault)); + titleFont.setPointSize(titleFont.pointSize() + 2); + titleLabel->setFont(titleFont); + }; + applyTitleFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, applyTitleFont); mainLayout->addWidget(titleLabel); if (!serverText.isEmpty()) { diff --git a/cockatrice/src/interface/widgets/dialogs/settings_view.cpp b/cockatrice/src/interface/widgets/dialogs/settings_view.cpp index d22a8d0b3..14d16b92c 100644 --- a/cockatrice/src/interface/widgets/dialogs/settings_view.cpp +++ b/cockatrice/src/interface/widgets/dialogs/settings_view.cpp @@ -7,6 +7,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../pixel_map_generator.h" +#include "../interface/theme_manager.h" #include "../settings_page/abstract_settings_page.h" #include "../settings_page/appearance_settings_page.h" #include "../settings_page/deck_editor_settings_page.h" @@ -199,6 +200,10 @@ void SettingsView::setupUi() searchDelegate->setPageIcons(icons); searchResultsView->viewport()->update(); }); + // The search delegate reads the InterfaceDefault role at paint time, so a + // font change only needs a repaint, mirroring the theme change above. + connect(themeManager, &ThemeManager::fontsChanged, searchResultsView, + [this] { searchResultsView->viewport()->update(); }); // Build search index after pages are created buildSearchIndex(); diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp index baf46bbba..5cb62db0f 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.cpp @@ -1,9 +1,11 @@ #include "banner_widget.h" #include "../../../pixel_map_generator.h" +#include "../../../theme_manager.h" #include #include +#include #include #include #include @@ -28,7 +30,9 @@ BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation if (transparency > 50) { textColor = " color: white;"; } - bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold;" + textColor); + bannerLabel->setStyleSheet(textColor); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &BannerWidget::applyInterfaceDefaultFont); layout->addWidget(iconLabel); layout->addWidget(bannerLabel); @@ -56,6 +60,16 @@ void BannerWidget::setText(const QString &text) const bannerLabel->setText(text); } +void BannerWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 24px size and + // bold weight previously carried by the stylesheet stay caller-side. + QFont bannerFont = themeManager->appFont(AppFont::InterfaceDefault); + bannerFont.setPixelSize(24); + bannerFont.setWeight(QFont::Bold); + bannerLabel->setFont(bannerFont); +} + void BannerWidget::setClickable(bool _clickable) { clickable = _clickable; diff --git a/cockatrice/src/interface/widgets/general/display/banner_widget.h b/cockatrice/src/interface/widgets/general/display/banner_widget.h index 06556089b..d46250bdb 100644 --- a/cockatrice/src/interface/widgets/general/display/banner_widget.h +++ b/cockatrice/src/interface/widgets/general/display/banner_widget.h @@ -48,6 +48,7 @@ signals: private slots: void toggleBuddyVisibility() const; void updateDropdownIconState() const; + void applyInterfaceDefaultFont(); }; #endif // BANNER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp index 484ac861d..6bb1dcd62 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp @@ -1,5 +1,7 @@ #include "bar_chart_widget.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -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 &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); diff --git a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp index 5a3130964..359d56772 100644 --- a/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp +++ b/cockatrice/src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp @@ -1,5 +1,7 @@ #include "segmented_bar_widget.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -22,6 +24,10 @@ SegmentedBarWidget::SegmentedBarWidget(QString label, QVector segments, setMinimumWidth(36); setMaximumWidth(50); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + // The label reads the InterfaceDefault role at paint time, so a font + // change only needs a repaint. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { update(); }); } QSize SegmentedBarWidget::sizeHint() const @@ -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); diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp index 0609eb16a..598c70fe2 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_label.cpp @@ -1,5 +1,7 @@ #include "dynamic_font_size_label.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -28,9 +30,13 @@ void DynamicFontSizeLabel::paintEvent(QPaintEvent *event) // QElapsedTimer timer; // timer.start(); - QFont newFont = font(); + // Family follows the InterfaceDefault theme role; the fitted point size + // stays computed from the widget geometry as before. + QFont newFont = themeManager->appFont(AppFont::InterfaceDefault); float fontSize = getWidgetMaximumFontSize(this, this->text()); newFont.setPointSizeF(fontSize); + // Preserve the caller's bold emphasis, which the role font does not carry. + newFont.setBold(font().bold()); setFont(newFont); // qDebug() << "Font size set to" << fontSize; @@ -40,7 +46,10 @@ void DynamicFontSizeLabel::paintEvent(QPaintEvent *event) float DynamicFontSizeLabel::getWidgetMaximumFontSize(QWidget *widget, const QString &text) { - QFont font = widget->font(); + // Measure with the InterfaceDefault family so the fit matches the painted font. + QFont font = themeManager->appFont(AppFont::InterfaceDefault); + font.setBold(widget->font().bold()); + font.setPointSizeF(widget->font().pointSizeF()); const QRect widgetRect = widget->contentsRect(); const float widgetWidth = widgetRect.width(); const float widgetHeight = widgetRect.height(); diff --git a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp index 25e8ca48f..9b5f5bdda 100644 --- a/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp +++ b/cockatrice/src/interface/widgets/general/display/dynamic_font_size_push_button.cpp @@ -1,5 +1,6 @@ #include "dynamic_font_size_push_button.h" +#include "../../interface/theme_manager.h" #include "dynamic_font_size_label.h" #include @@ -20,10 +21,12 @@ void DynamicFontSizePushButton::paintEvent(QPaintEvent *event) // Call the base class paintEvent to preserve any other painting behavior QPushButton::paintEvent(event); - // Adjust the font size dynamically based on the text - QFont newFont = font(); + // Adjust the font size dynamically based on the text. Family follows the + // InterfaceDefault theme role; the fitted size stays computed as before. + QFont newFont = themeManager->appFont(AppFont::InterfaceDefault); float fontSize = DynamicFontSizeLabel::getWidgetMaximumFontSize(this, this->text()); newFont.setPointSizeF(fontSize); + newFont.setBold(font().bold()); setFont(newFont); // Get painter for custom painting diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.cpp b/cockatrice/src/interface/widgets/general/home_styled_button.cpp index 7f23827bc..9c31d3815 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.cpp +++ b/cockatrice/src/interface/widgets/general/home_styled_button.cpp @@ -1,6 +1,9 @@ #include "home_styled_button.h" +#include "../../theme_manager.h" + #include +#include #include #include #include @@ -21,6 +24,18 @@ HomeStyledButton::HomeStyledButton(const QString &text, QPair _g setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setMinimumHeight(25); setStyleSheet(generateButtonStylesheet(gradientColors)); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &HomeStyledButton::applyInterfaceDefaultFont); +} + +void HomeStyledButton::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 34px size and + // bold weight previously carried by the stylesheet stay caller-side. + QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); + buttonFont.setPixelSize(34); + buttonFont.setWeight(QFont::Bold); + setFont(buttonFont); } void HomeStyledButton::updateStylesheet(const QPair &colors) @@ -50,9 +65,6 @@ QString HomeStyledButton::generateButtonStylesheet(const QPair & return QString(R"( QPushButton { - font-size: 34px; - font-weight: bold; - font-family: sans-serif, "Segoe UI", "Helvetica Neue"; padding: 30px; color: white; border: 2px solid %1; diff --git a/cockatrice/src/interface/widgets/general/home_styled_button.h b/cockatrice/src/interface/widgets/general/home_styled_button.h index 55ac5550f..5756db5a1 100644 --- a/cockatrice/src/interface/widgets/general/home_styled_button.h +++ b/cockatrice/src/interface/widgets/general/home_styled_button.h @@ -24,6 +24,9 @@ public: public slots: void paintEvent(QPaintEvent *event) override; +private slots: + void applyInterfaceDefaultFont(); + private: QPair gradientColors; }; diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp index 5905b2c40..5d7670c1f 100644 --- a/cockatrice/src/interface/widgets/general/home_widget.cpp +++ b/cockatrice/src/interface/widgets/general/home_widget.cpp @@ -289,7 +289,6 @@ QGroupBox *HomeWidget::createButtons() QGroupBox *box = new QGroupBox(this); box->setStyleSheet(R"( QGroupBox { - font-size: 20px; color: white; /* Title text color */ background: transparent; } @@ -300,6 +299,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); @@ -463,7 +470,9 @@ void HomeWidget::paintEvent(QPaintEvent *event) constexpr int margin = 15; constexpr qreal attributionMargin = 4.0; - QFont attributionFont = painter.font(); + // Family comes from the InterfaceDefault theme role; the 14px sizes and + // bold weights stay caller-side as before. + QFont attributionFont = themeManager->appFont(AppFont::InterfaceDefault); attributionFont.setPointSize(14); attributionFont.setBold(true); painter.setFont(attributionFont); @@ -476,7 +485,7 @@ void HomeWidget::paintEvent(QPaintEvent *event) // Card name bubble above the attribution (when enabled). if (!cardName.isEmpty() && SettingsCache::instance().appearance().getHomeTabDisplayCardName()) { - QFont font = painter.font(); + QFont font = themeManager->appFont(AppFont::InterfaceDefault); font.setPointSize(14); font.setBold(true); painter.setFont(font); diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp index c4298d6b6..e69687c54 100644 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp +++ b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp @@ -1,5 +1,6 @@ #include "first_run_wizard.h" +#include "../interface/theme_manager.h" #include "first_run_wizard_page.h" #include "pages/account_setup_page.h" #include "pages/card_database_setup_page.h" @@ -32,9 +33,8 @@ FirstRunWizard::FirstRunWizard(QWidget *parent) : QDialog(parent) bannerHost = new BannerHost(this); titleLabel = new QLabel(this); - QFont titleFont = QtUtils::boldFont(titleLabel->font()); - titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4); - titleLabel->setFont(titleFont); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &FirstRunWizard::applyInterfaceDefaultFont); subtitleLabel = new QLabel(this); subtitleLabel->setWordWrap(true); @@ -154,6 +154,16 @@ void FirstRunWizard::updateChrome() } } +void FirstRunWizard::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the *1.4 size + // multiplier and bold weight stay caller-side as before. + QFont titleFont = themeManager->appFont(AppFont::InterfaceDefault); + titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4); + titleFont.setBold(true); + titleLabel->setFont(titleFont); +} + void FirstRunWizard::goNext() { FirstRunWizardPage *page = pages[currentIndex]; diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h index 812985a9f..a5fa3b4bd 100644 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h +++ b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h @@ -55,6 +55,7 @@ private slots: void goBack(); void skip(); void updateChrome(); + void applyInterfaceDefaultFont(); private: void addPage(FirstRunWizardPage *page); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp index bad88ddbb..13a43bd47 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -1,5 +1,6 @@ #include "all_zones_card_amount_widget.h" +#include "../../theme_manager.h" #include "../general/display/shadow_background_label.h" #include "card_amount_widget.h" #include "libcockatrice/card/card_info.h" @@ -67,6 +68,7 @@ AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, buttonBoxSideboard->setVisible(!isToken); connect(cardSizeSlider, &QSlider::valueChanged, this, &AllZonesCardAmountWidget::adjustFontSize); + connect(themeManager, &ThemeManager::fontsChanged, this, &AllZonesCardAmountWidget::applyCardTextFont); QTimer::singleShot(10, this, [this]() { adjustFontSize(this->cardSizeSlider->value()); }); @@ -90,8 +92,9 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); - // Update the font labels - QFont zoneLabelFont = zoneLabelMainboard->font(); + // Update the font labels. The family comes from the CardText theme + // role; the slider-driven point size stays as before. + QFont zoneLabelFont = themeManager->appFont(AppFont::CardText); zoneLabelFont.setPointSize(newFontSize); zoneLabelMainboard->setFont(zoneLabelFont); zoneLabelSideboard->setFont(zoneLabelFont); @@ -101,6 +104,11 @@ void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) repaint(); } +void AllZonesCardAmountWidget::applyCardTextFont() +{ + adjustFontSize(cardSizeSlider->value()); +} + void AllZonesCardAmountWidget::setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount) { buttonBoxMainboard->setAmount(mainboardAmount); diff --git a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h index cefabe293..3213cf16b 100644 --- a/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/all_zones_card_amount_widget.h @@ -36,6 +36,9 @@ public slots: void adjustFontSize(int scalePercentage); void setAmounts(int mainboardAmount, int sideboardAmount, int tokensboardAmount); +private slots: + void applyCardTextFont(); + private: QVBoxLayout *layout; QSlider *cardSizeSlider; diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index eae49d7ef..7f25ea1f4 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -1,5 +1,6 @@ #include "card_amount_widget.h" +#include "../../theme_manager.h" #include "../deck_editor/deck_state_manager.h" #include "../general/display/dynamic_font_size_push_button.h" #include "deck_list_model.h" @@ -77,6 +78,7 @@ CardAmountWidget::CardAmountWidget(QWidget *parent, // Connect slider for dynamic font size adjustment connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); + connect(themeManager, &ThemeManager::fontsChanged, this, &CardAmountWidget::applyCardTextFont); } int CardAmountWidget::getAmount() @@ -138,8 +140,9 @@ void CardAmountWidget::adjustFontSize(int scalePercentage) int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); - // Update the font for card count label - QFont cardCountFont = cardCountInZone->font(); + // Update the font for card count label. The family comes from the + // CardText theme role; the slider-driven point size stays as before. + QFont cardCountFont = themeManager->appFont(AppFont::CardText); cardCountFont.setPointSize(newFontSize); cardCountInZone->setFont(cardCountFont); @@ -150,6 +153,11 @@ void CardAmountWidget::adjustFontSize(int scalePercentage) repaint(); } +void CardAmountWidget::applyCardTextFont() +{ + adjustFontSize(cardSizeSlider->value()); +} + /** * @brief Updates the card count display in the widget. */ diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h index 6f5889fc7..cef872157 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.h @@ -67,6 +67,7 @@ private slots: void removePrintingSideboard(); void removePrintingTokensboard(); void adjustFontSize(int scalePercentage); + void applyCardTextFont(); }; #endif // CARD_AMOUNT_WIDGET_H diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp index 7f9ad66c8..225c3e21d 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp @@ -1,5 +1,8 @@ #include "printing_selector_placeholder_widget.h" +#include "../interface/theme_manager.h" + +#include #include #include #include @@ -30,10 +33,12 @@ PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *pa textLabel->setStyleSheet(R"( QLabel { color: palette(mid); - font-size: 14px; padding: 10px; } )"); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont); mainLayout->addWidget(imageLabel); mainLayout->addWidget(textLabel); @@ -44,4 +49,12 @@ PrintingSelectorPlaceholderWidget::PrintingSelectorPlaceholderWidget(QWidget *pa void PrintingSelectorPlaceholderWidget::retranslateUi() { textLabel->setText(tr("Select a card to view its available printings")); +} + +void PrintingSelectorPlaceholderWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 14px size stays caller-side. + QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault); + placeholderFont.setPixelSize(14); + textLabel->setFont(placeholderFont); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h index 6702d325a..9e6e47bcb 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_placeholder_widget.h @@ -14,6 +14,9 @@ class PrintingSelectorPlaceholderWidget : public QWidget public: explicit PrintingSelectorPlaceholderWidget(QWidget *parent = nullptr); +private slots: + void applyInterfaceDefaultFont(); + private: QVBoxLayout *mainLayout; QLabel *imageLabel; diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index 037a4a1e5..c57f65352 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -4,6 +4,7 @@ #include "../../client/sound_engine.h" #include "../../interface/card_localization.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "../../tabs/tab_supervisor.h" #include "../user/user_context_menu.h" #include "../user/user_list_manager.h" @@ -63,6 +64,9 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _sho connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this, &ChatView::adjustColorsToPalette); + applyChatFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &ChatView::applyChatFont); + userContextMenu = new UserContextMenu(tabSupervisor, this, game); connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); @@ -108,6 +112,16 @@ void ChatView::adjustColorsToPalette() QTimer::singleShot(0, this, &ChatView::refreshBlockColors); } +void ChatView::applyChatFont() +{ + // Family comes from the ChatLog theme role; the message char formats carry + // no family of their own, so they (and new content) follow the widget and + // document default fonts. Weights/italics stay caller-side as before. + const QFont chatFont = themeManager->appFont(AppFont::ChatLog); + setFont(chatFont); + document()->setDefaultFont(chatFont); +} + void ChatView::refreshBlockColors() { QTextDocument *doc = document(); diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h index 3fa7224e5..a2e2f33dd 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h @@ -111,6 +111,7 @@ private slots: void openLink(const QUrl &link); void actMessageClicked(); void adjustColorsToPalette(); + void applyChatFont(); void refreshBlockColors(); void onScrollBarRangeChanged(); void onScrollBarValueChanged(int value); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp index fa7698037..997001c50 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.cpp @@ -1,6 +1,7 @@ #include "user_info_box.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "../../interface/widgets/dialogs/dlg_edit_avatar.h" #include "../../interface/widgets/dialogs/dlg_edit_password.h" #include "../../interface/widgets/dialogs/dlg_edit_user.h" @@ -39,10 +40,8 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), client(_client), editable(_editable) { - QFont nameFont = nameLabel.font(); - nameFont.setBold(true); - nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5); - nameLabel.setFont(nameFont); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &UserInfoBox::applyInterfaceDefaultFont); auto *userIconAndNameLayout = new QHBoxLayout; userIconAndNameLayout->addWidget(&userLevelIcon, 0, Qt::AlignCenter); @@ -91,6 +90,16 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *paren retranslateUi(); } +void UserInfoBox::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the *1.5 size + // multiplier and bold weight stay caller-side as before. + QFont nameFont = themeManager->appFont(AppFont::InterfaceDefault); + nameFont.setBold(true); + nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5); + nameLabel.setFont(nameFont); +} + void UserInfoBox::retranslateUi() { realNameLabel1.setText(tr("Real Name:")); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_box.h b/cockatrice/src/interface/widgets/server/user/user_info_box.h index 6bc571a37..98919b027 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_box.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_box.h @@ -48,6 +48,7 @@ private slots: void processEditResponse(const Response &r); void processPasswordResponse(const Response &r); void processAvatarResponse(const Response &r); + void applyInterfaceDefaultFont(); void actEdit(); void actEditInternal(const Response &r); diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp index efb3b2e31..f6e6508e2 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp @@ -142,7 +142,8 @@ public: p->drawEllipse(QRectF(rect.left() + 9, rect.top() + (rect.height() - 8) / 2.0, 8, 8)); // Game title (bold, elided) - QFont tf = option.font; + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont tf = themeManager->appFont(AppFont::InterfaceDefault); tf.setBold(true); p->setFont(tf); p->setPen(pal.color(QPalette::Text)); @@ -154,7 +155,7 @@ public: // Player count const bool full = game.player_count() >= game.max_players(); - p->setFont(option.font); + p->setFont(themeManager->appFont(AppFont::InterfaceDefault)); p->setPen(full ? QColor(249, 115, 22) : pal.color(QPalette::Disabled, QPalette::Text)); p->drawText(QRect(rect.right() - countW - 4, rect.top(), countW, rect.height()), Qt::AlignVCenter | Qt::AlignRight, @@ -323,7 +324,8 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) const int tx = ax + AvatarSize + AvatarToTextGap; const int tw = rect.width() - tx - 8; - QFont nf = font(); + // Family comes from the InterfaceDefault theme role; the *1.12 size and bold stay caller-side. + QFont nf = themeManager->appFont(AppFont::InterfaceDefault); nf.setBold(true); nf.setPointSizeF(nf.pointSizeF() * 1.12); p.setFont(nf); @@ -352,7 +354,7 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) } if (!badge.text.isEmpty()) { - QFont bf = font(); + QFont bf = themeManager->appFont(AppFont::InterfaceDefault); bf.setPointSizeF(bf.pointSizeF() * 0.70); bf.setBold(true); p.setFont(bf); @@ -369,7 +371,8 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) // The painter font at this point depends on whether a badge was drawn // (badge font vs username font), so pin an explicit font for the pill. - p.setFont(font()); + // Family follows the InterfaceDefault theme role like the rest of the popup chrome. + p.setFont(themeManager->appFont(AppFont::InterfaceDefault)); // Only show the attribution when there is actually art on screen: on a // cache miss cardArt is null and the pill would float over the plain @@ -398,6 +401,7 @@ UserInfoPopup::UserInfoPopup(TabSupervisor *_ts, // Restyle the popup chrome when the theme or its color scheme changes. if (themeManager) { connect(themeManager, &ThemeManager::themeChanged, this, &UserInfoPopup::applyTheme); + connect(themeManager, &ThemeManager::fontsChanged, this, &UserInfoPopup::applyTheme); } } @@ -475,15 +479,24 @@ void UserInfoPopup::applyTheme() separator->setStyleSheet(QStringLiteral("color:%1; margin: 0 8px;").arg(colorStr(theme.border))); - gamesLabel->setStyleSheet(QStringLiteral("color:%1; font-size:11px; font-weight:bold; background:transparent;") - .arg(colorStr(theme.subText))); + gamesLabel->setStyleSheet(QStringLiteral("color:%1; background:transparent;").arg(colorStr(theme.subText))); + QFont gamesLabelFont = themeManager->appFont(AppFont::InterfaceDefault); + gamesLabelFont.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;}") @@ -491,9 +504,12 @@ void UserInfoPopup::applyTheme() closeBtn->setStyleSheet( QStringLiteral("QPushButton{background:%1;color:%2;" - "border:none;border-radius:11px;font-size:10px;}" + "border:none;border-radius:11px;}" "QPushButton:hover{color:%3;background:%4;}") .arg(colorStr(theme.closeBg), colorStr(theme.subText), colorStr(theme.text), colorStr(theme.closeHover))); + QFont closeFont = themeManager->appFont(AppFont::InterfaceDefault); + closeFont.setPixelSize(10); + closeBtn->setFont(closeFont); header->update(); } @@ -508,13 +524,17 @@ static QPushButton *makeBtn(const QString &label, const QString &tip, QWidget *p b->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); b->setStyleSheet(QStringLiteral("QPushButton{" " background:%1;color:%2;border:1px solid %3;" - " border-radius:4px;font-size:11px;padding:0 4px;" + " border-radius:4px;padding:0 4px;" "}" "QPushButton:hover{background:%4;color:%5;}" "QPushButton:pressed{background:%6;}" "QPushButton:disabled{color:%7;border-color:%3;}") .arg(colorStr(t.buttonBg), colorStr(t.text), colorStr(t.buttonBorder), colorStr(t.buttonHover), colorStr(t.text), colorStr(t.buttonPressed), colorStr(t.buttonDisabled))); + // Family comes from the InterfaceDefault theme role; the 11px size stays caller-side. + QFont btnFont = themeManager->appFont(AppFont::InterfaceDefault); + btnFont.setPixelSize(11); + b->setFont(btnFont); return b; } diff --git a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp index 08eaaaff7..acb91351b 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp @@ -1,6 +1,7 @@ #include "user_list_painter.h" #include "../../interface/pixel_map_generator.h" +#include "../../interface/theme_manager.h" #include "libcockatrice/protocol/pb/serverinfo_user.pb.h" #include @@ -292,7 +293,9 @@ void UserListPainter::drawUserName(QPainter *painter, bool online, const Style &style) { - QFont nameFont = option.font; + Q_UNUSED(option); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont nameFont = themeManager->appFont(AppFont::InterfaceDefault); nameFont.setBold(true); painter->setFont(nameFont); @@ -351,7 +354,9 @@ void UserListPainter::drawBadges(QPainter *painter, return; } - QFont badgeFont = option.font; + Q_UNUSED(option); + // Family comes from the InterfaceDefault theme role; the *0.68 size and bold stay caller-side. + QFont badgeFont = themeManager->appFont(AppFont::InterfaceDefault); badgeFont.setPointSizeF(badgeFont.pointSizeF() * 0.68); badgeFont.setBold(true); painter->setFont(badgeFont); diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index a20b39b82..34e542e02 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -519,6 +519,19 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, userTree->viewport()->update(); userTree->update(); }); + // Divider headers store their font on the item, so re-apply the role + // family here; the painter reads the role live and needs only the + // repaint below. + connect(themeManager, &ThemeManager::fontsChanged, this, [this] { + QFont dividerFont = themeManager->appFont(AppFont::InterfaceDefault); + dividerFont.setBold(true); + for (auto it = sectionItems.constBegin(); it != sectionItems.constEnd(); ++it) { + it.value()->setFont(0, dividerFont); + it.value()->setSizeHint(0, QSize(0, QFontMetrics(dividerFont).height() + 16)); + } + userTree->viewport()->update(); + userTree->update(); + }); } QVBoxLayout *vbox = new QVBoxLayout; @@ -1492,7 +1505,7 @@ QTreeWidgetItem *UserListWidget::createSectionItem(Section section) // They act as collapsible section headers once they have focus. divider->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - QFont font = userTree->font(); + QFont font = themeManager->appFont(AppFont::InterfaceDefault); font.setBold(true); divider->setFont(0, font); // A little taller than a plain text row so the header reads as a section diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp index de5f67b32..d2214a5af 100644 --- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.cpp @@ -283,20 +283,11 @@ AppearanceSettingsPage::AppearanceSettingsPage() connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings.cardsDisplay(), &CardsDisplaySettings::setRoundCardCorners); - connect(&maxFontSizeForCardsEdit, qOverload(&QSpinBox::valueChanged), &settings.appearance(), - &AppearanceSettings::setMaxFontSize); - maxFontSizeForCardsEdit.setValue(settings.appearance().getMaxFontSize()); - maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit); - maxFontSizeForCardsEdit.setMinimum(9); - maxFontSizeForCardsEdit.setMaximum(100); - auto *cardsGrid = new QGridLayout; cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2); cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2); cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2); cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2); - cardsGrid->addWidget(&maxFontSizeForCardsLabel, 4, 0, 1, 1); - cardsGrid->addWidget(&maxFontSizeForCardsEdit, 4, 1, 1, 1); cardsGroupBox = new QGroupBox; cardsGroupBox->setLayout(cardsGrid); @@ -657,7 +648,6 @@ void AppearanceSettingsPage::retranslateUi() autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout")); cardScalingCheckBox.setText(tr("Scale cards on mouse over")); roundCardCornersCheckBox.setText(tr("Use rounded card corners")); - maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:")); cardLayoutGroupBox->setTitle(tr("Card layout")); verticalCardOverlapPercentLabel.setText( diff --git a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h index f919a2a2f..8d008dfeb 100644 --- a/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/appearance_settings_page.h @@ -71,8 +71,6 @@ private: QCheckBox autoRotateSidewaysLayoutCardsCheckBox; QCheckBox cardScalingCheckBox; QCheckBox roundCardCornersCheckBox; - QLabel maxFontSizeForCardsLabel; - QSpinBox maxFontSizeForCardsEdit; QLabel verticalCardOverlapPercentLabel; QSpinBox verticalCardOverlapPercentBox; diff --git a/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp b/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp index bf35b7557..67354c9d7 100644 --- a/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp +++ b/cockatrice/src/interface/widgets/settings_page/settings_search_delegate.cpp @@ -5,6 +5,7 @@ */ #include "settings_search_delegate.h" +#include "../interface/theme_manager.h" #include "settings_search_model.h" #include @@ -88,7 +89,8 @@ void SettingsSearchDelegate::paint(QPainter *painter, QRect textRect = contentRect.adjusted(iconOffset, 0, 0, 0); // Breadcrumb: "Page > Group" - QFont breadcrumbFont = option.font; + // Family comes from the InterfaceDefault theme role; the -1 size and bold stay caller-side. + QFont breadcrumbFont = themeManager->appFont(AppFont::InterfaceDefault); breadcrumbFont.setPointSize(breadcrumbFont.pointSize() - 1); breadcrumbFont.setBold(true); @@ -113,7 +115,7 @@ void SettingsSearchDelegate::paint(QPainter *painter, yPos += 20; // Setting label - QFont labelFont = option.font; + QFont labelFont = themeManager->appFont(AppFont::InterfaceDefault); labelFont.setPointSize(labelFont.pointSize() + 1); labelFont.setBold(isSelected); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp index 97383534f..3720aeb55 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp @@ -1,5 +1,6 @@ #include "archidekt_deck_preview_image_display_widget.h" +#include "../../../../../theme_manager.h" #include "../../../../general/display/shadow_background_label.h" #include @@ -17,13 +18,9 @@ ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(Q bottomLeftLabel = new ShadowBackgroundLabel(this, ""); bottomRightLabel = new ShadowBackgroundLabel(this, ""); - QFont f; - f.setBold(true); - f.setPointSize(16); - topLeftLabel->setFont(f); - topRightLabel->setFont(f); - bottomLeftLabel->setFont(f); - bottomRightLabel->setFont(f); + applyCardTextFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &ArchidektDeckPreviewImageDisplayWidget::applyCardTextFont); // Raise so labels appear above image topLeftLabel->raise(); @@ -32,6 +29,18 @@ ArchidektDeckPreviewImageDisplayWidget::ArchidektDeckPreviewImageDisplayWidget(Q bottomRightLabel->raise(); } +void ArchidektDeckPreviewImageDisplayWidget::applyCardTextFont() +{ + // Family comes from the CardText theme role; size and weight stay caller-side as before. + QFont f = themeManager->appFont(AppFont::CardText); + f.setBold(true); + f.setPointSize(16); + topLeftLabel->setFont(f); + topRightLabel->setFont(f); + bottomLeftLabel->setFont(f); + bottomRightLabel->setFont(f); +} + void ArchidektDeckPreviewImageDisplayWidget::setAspectRatio(float ratio) { aspectRatio = ratio; diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h index 991f27b6a..41585e851 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.h @@ -60,6 +60,9 @@ protected: */ void resizeEvent(QResizeEvent *event) override; +private slots: + void applyCardTextFont(); + private: float aspectRatio = 1.0f; ///< Aspect ratio to maintain for the preview image }; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index b2ddace01..2b76de973 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -1,5 +1,6 @@ #include "edhrec_api_response_card_details_display_widget.h" +#include "../../../../../../theme_manager.h" #include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_size_widget.h" #include "../../../../../general/display/background_plate_widget.h" @@ -9,6 +10,7 @@ #include "edhrec_api_response_card_synergy_display_widget.h" #include "libcockatrice/card/database/card_database_querier.h" +#include #include #include #include @@ -32,7 +34,9 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi nameLabel = new QLabel(this); nameLabel->setText(toDisplay.name); nameLabel->setAlignment(Qt::AlignHCenter); - nameLabel->setStyleSheet("font-size: 20px; font-weight: bold"); + applyCardTextFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecApiResponseCardDetailsDisplayWidget::applyCardTextFont); inclusionDisplayWidget = new EdhrecApiResponseCardInclusionDisplayWidget(this, toDisplay); @@ -86,3 +90,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); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h index d738f4685..8a3c8fba4 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.h @@ -30,6 +30,9 @@ public slots: signals: void requestUrl(QString url); +private slots: + void applyCardTextFont(); + private: EdhrecApiResponseCardDetails toDisplay; QVBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index 2ff22f2b3..df0319f88 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -1,5 +1,6 @@ #include "edhrec_api_response_commander_details_display_widget.h" +#include "../../../../../../theme_manager.h" #include "../../../../../cards/card_info_picture_widget.h" #include "../../../../../cards/card_size_widget.h" #include "../../api_response/cards/edhrec_commander_api_response_commander_details.h" @@ -8,6 +9,7 @@ #include "edhrec_commander_api_response_navigation_widget.h" #include "libcockatrice/card/database/card_database_querier.h" +#include #include #include #include @@ -48,15 +50,16 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom commanderName = new QLabel(this); commanderName->setText(commanderDetails.getName()); commanderName->setAlignment(Qt::AlignCenter); - commanderName->setStyleSheet("font-size: 28px; font-weight: bold"); label = new QLabel(this); label->setAlignment(Qt::AlignCenter); - label->setStyleSheet("font-size: 16px"); salt = new QLabel(this); salt->setAlignment(Qt::AlignCenter); - salt->setStyleSheet("font-size: 16px"); + + applyCardTextFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecCommanderResponseCommanderDetailsDisplayWidget::applyCardTextFont); cardPricesDisplayWidget = new EdhrecApiResponseCardPricesDisplayWidget(this, commanderDetails.getPrices()); @@ -83,3 +86,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); +} diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h index 197263512..99e6b3d43 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.h @@ -30,6 +30,9 @@ public: QString baseUrl); void retranslateUi(); +private slots: + void applyCardTextFont(); + private: EdhrecCommanderApiResponseCommanderDetails commanderDetails; QHBoxLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp index b26a7e574..749476619 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp @@ -1,6 +1,9 @@ #include "edhrec_commander_api_response_bracket_navigation_widget.h" +#include "../../interface/theme_manager.h" + #include +#include #include #include #include @@ -25,7 +28,7 @@ EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBra QString label = option.isEmpty() ? "All" : option.at(0).toUpper() + option.mid(1); QPushButton *optionButton = new QPushButton(label, this); optionButton->setMinimumHeight(84); - optionButton->setStyleSheet("font-size: 24px"); + optionButton->setStyleSheet(QString()); gameChangerButtons[option] = optionButton; layout->addWidget(optionButton, 2, i); connect(optionButton, &QPushButton::clicked, this, [=, this]() { @@ -35,6 +38,10 @@ EdhrecCommanderApiResponseBracketNavigationWidget::EdhrecCommanderApiResponseBra }); } + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecCommanderApiResponseBracketNavigationWidget::applyInterfaceDefaultFont); + updateOptionButtonSelection(gameChangerButtons, ""); retranslateUi(); @@ -94,8 +101,18 @@ void EdhrecCommanderApiResponseBracketNavigationWidget::updateOptionButtonSelect const QString &selectedKey) { for (auto it = buttons.begin(); it != buttons.end(); ++it) { - it.value()->setStyleSheet(it.key() == selectedKey - ? "background-color: lightblue; font-weight: bold; font-size: 24px;" - : "font-size: 24px"); + const bool selected = (it.key() == selectedKey); + it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString()); + QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); + buttonFont.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); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h index a741b6696..aa4e61ff1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h @@ -37,6 +37,9 @@ private: QMap gameChangerButtons; void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); + +private slots: + void applyInterfaceDefaultFont(); }; #endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BRACKET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp index 48704d010..27cd1c51f 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp @@ -1,6 +1,9 @@ #include "edhrec_commander_api_response_budget_navigation_widget.h" +#include "../../interface/theme_manager.h" + #include +#include #include #include #include @@ -25,7 +28,7 @@ EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudg QString label = option.isEmpty() ? "Any" : option.at(0).toUpper() + option.mid(1); QPushButton *btn = new QPushButton(label, this); btn->setMinimumHeight(84); - btn->setStyleSheet("font-size: 24px"); + btn->setStyleSheet(QString()); budgetButtons[option] = btn; layout->addWidget(btn, 4, i); connect(btn, &QPushButton::clicked, this, [=, this]() { @@ -35,6 +38,10 @@ EdhrecCommanderApiResponseBudgetNavigationWidget::EdhrecCommanderApiResponseBudg }); } + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecCommanderApiResponseBudgetNavigationWidget::applyInterfaceDefaultFont); + updateOptionButtonSelection(budgetButtons, ""); retranslateUi(); @@ -96,8 +103,18 @@ void EdhrecCommanderApiResponseBudgetNavigationWidget::updateOptionButtonSelecti const QString &selectedKey) { for (auto it = buttons.begin(); it != buttons.end(); ++it) { - it.value()->setStyleSheet(it.key() == selectedKey - ? "background-color: lightblue; font-weight: bold; font-size: 24px;" - : "font-size: 24px"); + const bool selected = (it.key() == selectedKey); + it.value()->setStyleSheet(selected ? QStringLiteral("background-color: lightblue;") : QString()); + QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault); + buttonFont.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); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h index 8013d1d6b..d727225a5 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h @@ -37,6 +37,9 @@ private: QMap budgetButtons; void updateOptionButtonSelection(QMap &buttons, const QString &selectedKey); + +private slots: + void applyInterfaceDefaultFont(); }; #endif // COCKATRICE_EDHREC_COMMANDER_API_RESPONSE_BUDGET_NAVIGATION_WIDGET_H diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp index 220dcfbda..57070a5e1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp @@ -1,9 +1,11 @@ #include "edhrec_commander_api_response_navigation_widget.h" +#include "../../interface/theme_manager.h" #include "../../tab_edhrec_main.h" #include "edhrec_commander_api_response_bracket_navigation_widget.h" #include "edhrec_commander_api_response_budget_navigation_widget.h" +#include #include #include #include @@ -30,10 +32,14 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation comboPushButton = new QPushButton(this); comboPushButton->setMinimumHeight(84); - comboPushButton->setStyleSheet("font-size: 24px"); + comboPushButton->setStyleSheet(QString()); averageDeckPushButton = new QPushButton(this); averageDeckPushButton->setMinimumHeight(84); - averageDeckPushButton->setStyleSheet("font-size: 24px"); + averageDeckPushButton->setStyleSheet(QString()); + + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont); layout->addWidget(comboPushButton, 0, 0, 1, 1); layout->addWidget(averageDeckPushButton, 0, 1, 1, 1); @@ -61,6 +67,15 @@ void EdhrecCommanderApiResponseNavigationWidget::retranslateUi() averageDeckPushButton->setText(tr("Average Deck")); } +void EdhrecCommanderApiResponseNavigationWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 24px size stays caller-side. + QFont navFont = themeManager->appFont(AppFont::InterfaceDefault); + navFont.setPixelSize(24); + comboPushButton->setFont(navFont); + averageDeckPushButton->setFont(navFont); +} + QString EdhrecCommanderApiResponseNavigationWidget::addNavigationOptionsToUrl(QString baseUrl) { if (!bracketNavigationWidget->getSelectedGameChanger().isEmpty()) { diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h index 737558625..456228554 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.h @@ -35,6 +35,9 @@ public slots: signals: void requestUrl(QString url); +private slots: + void applyInterfaceDefaultFont(); + private: QGridLayout *layout; diff --git a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp index d641a1ea5..024cd46f0 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_developer.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_developer.cpp @@ -6,6 +6,7 @@ #include "tab_developer.h" +#include "../interface/theme_manager.h" #include "libcockatrice/protocol/pb/response.pb.h" #include @@ -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 §ionTitle) const int row = statsTable->rowCount(); statsTable->insertRow(row); auto *labelItem = new QTableWidgetItem(sectionTitle); - auto font = labelItem->font(); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + QFont font = themeManager->appFont(AppFont::InterfaceDefault); font.setBold(true); labelItem->setFont(font); labelItem->setFlags(labelItem->flags() & ~Qt::ItemIsSelectable); diff --git a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp index ce37ce440..dc14b6e4a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp @@ -4,6 +4,7 @@ #include "../../deck_loader/deck_loader.h" #include "../cards/card_size_widget.h" #include "../general/layout_containers/flow_widget.h" +#include "../interface/theme_manager.h" #include "../visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h" #include "../visual_deck_storage/deck_preview/public_deck_preview_widget.h" #include "../visual_deck_storage/remote_public_decks_model.h" @@ -45,7 +46,17 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c cardSize = SettingsCache::instance().cardsDisplay().getVisualDeckStorageCardSize(); titleLabel = new QLabel(tr("Public decks of %1").arg(userName.toHtmlEscaped()), this); - titleLabel->setFont(QtUtils::boldFont(titleLabel->font())); + // Family comes from the InterfaceDefault theme role; bold stays caller-side. + auto applyTitleFont = [this]() { + titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); + }; + applyTitleFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, [this, applyTitleFont] { + applyTitleFont(); + // Preview cells paint with the live role font; repaint the grid so the + // new family shows without waiting for a scroll or hover. + flowWidget->update(); + }); auto *headerLayout = new QHBoxLayout; headerLayout->addWidget(titleLabel); diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.cpp b/cockatrice/src/interface/widgets/tabs/tab_report.cpp index 988c3e7d9..7986286b3 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_report.cpp @@ -1,5 +1,6 @@ #include "tab_report.h" +#include "../interface/theme_manager.h" #include "../server/user/user_context_menu.h" #include "../utility/report_utils.h" #include "abstract_client.h" @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -137,7 +139,8 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T chatGroup = new QGroupBox; chatLogEdit = new QTextEdit; chatLogEdit->setReadOnly(true); - chatLogEdit->setFont(QtUtils::monospaceFont()); + applyChatLogFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, &TabReport::applyChatLogFont); auto *chatLayout = new QVBoxLayout(chatGroup); chatLayout->setContentsMargins(4, 4, 4, 4); chatLayout->addWidget(chatLogEdit); @@ -269,6 +272,11 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T refreshList(); } +void TabReport::applyChatLogFont() +{ + chatLogEdit->setFont(report_utils::reportLogFont()); +} + void TabReport::retranslateUi() { userContextMenu->retranslateUi(); diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.h b/cockatrice/src/interface/widgets/tabs/tab_report.h index 32ebe1a3f..43e778f4b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.h +++ b/cockatrice/src/interface/widgets/tabs/tab_report.h @@ -62,6 +62,7 @@ private slots: void statsResponse(const Response &response); void reopenReport(); void reopenResponse(const Response &response); + void applyChatLogFont(); private: int selectedReportId() const; diff --git a/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp b/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp index a629df43c..0090e9d65 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp +++ b/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp @@ -1,5 +1,6 @@ #include "card_completer_delegate.h" +#include "../../interface/theme_manager.h" #include "../../pixel_map_generator.h" #include "../cards/additional_info/mana_cost_widget.h" #include "libcockatrice/card/database/card_database_querier.h" @@ -182,7 +183,7 @@ void CardCompleterDelegate::drawManaSymbol(QPainter *p, QPoint centre, const QSt p->setBrush(Qt::NoBrush); p->drawEllipse(pip.adjusted(1, 1, -1, -1)); - QFont f = QtUtils::boldFont(p->font()); + QFont f = QtUtils::boldFont(themeManager->appFont(AppFont::CardText)); f.setPixelSize(qMax(radius - 1, 7)); p->setFont(f); @@ -429,7 +430,7 @@ void CardCompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem const QRect nameRect = showInfoBand ? QRect(textLeft, rect.top() + 2, textWidth, 20) : QRect(textLeft, rect.top() + 2, textWidth, rect.height() - 4); - QFont f = option.font; + QFont f = themeManager->appFont(AppFont::CardText); f.setPixelSize(13); f.setBold(true); @@ -451,7 +452,7 @@ void CardCompleterDelegate::paint(QPainter *painter, const QStyleOptionViewItem if (showInfoBand) { const QRect infoRect(textLeft, rect.top() + 22, textWidth, rect.height() - 24); - QFont f = option.font; + QFont f = themeManager->appFont(AppFont::CardText); f.setPixelSize(10); painter->setFont(f); diff --git a/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp b/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp index 8f1941412..1a17bb8be 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp +++ b/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp @@ -1,5 +1,6 @@ #include "card_completer_styler.h" +#include "../../interface/theme_manager.h" #include "../cards/card_info_picture_enlarged_widget.h" #include "card_completer_delegate.h" #include "libcockatrice/card/database/card_database_querier.h" @@ -73,6 +74,10 @@ CardCompleterStyler::CardCompleterStyler(QCompleter *completer, QObject *parent) popup->viewport()->setMouseTracking(true); + // The delegate reads the CardText role at paint time, so a font change + // only needs a repaint of the popup. + connect(themeManager, &ThemeManager::fontsChanged, popup, [popup] { popup->viewport()->update(); }); + popup->installEventFilter(this); popup->viewport()->installEventFilter(this); diff --git a/cockatrice/src/interface/widgets/utility/report_utils.cpp b/cockatrice/src/interface/widgets/utility/report_utils.cpp index d7fc083c6..df2400b14 100644 --- a/cockatrice/src/interface/widgets/utility/report_utils.cpp +++ b/cockatrice/src/interface/widgets/utility/report_utils.cpp @@ -1,5 +1,6 @@ #include "report_utils.h" +#include "../../theme_manager.h" #include "libcockatrice/protocol/pb/serverinfo_report.pb.h" #include @@ -7,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +21,20 @@ namespace report_utils { +QFont reportLogFont() +{ + if (themeManager->hasAppFont(AppFont::ChatLog)) { + return themeManager->appFont(AppFont::ChatLog); + } + QFont monoFont(QStringLiteral("monospace")); + monoFont.setStyleHint(QFont::Monospace); + const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); + if (systemPointSize > 0) { + monoFont.setPointSize(systemPointSize); + } + return monoFont; +} + namespace { QColor reportStatusColor(const QString &status) diff --git a/cockatrice/src/interface/widgets/utility/report_utils.h b/cockatrice/src/interface/widgets/utility/report_utils.h index 679989486..32cc19eac 100644 --- a/cockatrice/src/interface/widgets/utility/report_utils.h +++ b/cockatrice/src/interface/widgets/utility/report_utils.h @@ -1,6 +1,7 @@ #ifndef REPORT_UTILS_H #define REPORT_UTILS_H +#include #include #include @@ -11,6 +12,12 @@ class ServerInfo_Report; namespace report_utils { +/** @brief Font for the report chat-log views: the theme's ChatLog role when it + * sets one, otherwise the monospace font these views used before the + * role existed. Shared by the three report surfaces so they cannot + * drift apart. */ +QFont reportLogFont(); + QString formatReportCategory(const QString &category); QString formatReportTime(qint64 secondsSinceEpoch); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp index 76c3d0908..d3c087bc6 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp @@ -1,5 +1,8 @@ #include "visual_deck_editor_placeholder_widget.h" +#include "../interface/theme_manager.h" + +#include #include #include #include @@ -28,10 +31,12 @@ VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *pa textLabel->setStyleSheet(R"( QLabel { color: palette(mid); - font-size: 14px; padding: 10px; } )"); + applyInterfaceDefaultFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, + &VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont); mainLayout->addWidget(imageLabel); mainLayout->addWidget(textLabel); @@ -42,4 +47,12 @@ VisualDeckEditorPlaceholderWidget::VisualDeckEditorPlaceholderWidget(QWidget *pa void VisualDeckEditorPlaceholderWidget::retranslateUi() { textLabel->setText(tr("Add cards using the search bar or database tab to have them appear here")); +} + +void VisualDeckEditorPlaceholderWidget::applyInterfaceDefaultFont() +{ + // Family comes from the InterfaceDefault theme role; the 14px size stays caller-side. + QFont placeholderFont = themeManager->appFont(AppFont::InterfaceDefault); + placeholderFont.setPixelSize(14); + textLabel->setFont(placeholderFont); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h index 84a3142d8..794be671e 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.h @@ -15,6 +15,9 @@ public: explicit VisualDeckEditorPlaceholderWidget(QWidget *parent = nullptr); void retranslateUi(); +private slots: + void applyInterfaceDefaultFont(); + private: QVBoxLayout *mainLayout; QLabel *imageLabel; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp index 82ed6191c..30e99f401 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp @@ -1,5 +1,7 @@ #include "deck_preview_tag_addition_widget.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -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); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp index e62570241..34809da2e 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp @@ -1,5 +1,7 @@ #include "deck_preview_tag_display_widget.h" +#include "../../interface/theme_manager.h" + #include #include #include @@ -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); diff --git a/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp b/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp index 14969dae6..4de87991c 100644 --- a/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/appearance_settings.cpp @@ -33,16 +33,6 @@ void AppearanceSettings::setStyleUserList(bool _styleUserList) emit styleUserListChanged(); } -int AppearanceSettings::getMaxFontSize() const -{ - return getValue("maxFontSize", QString(), QString(), 12).toInt(); -} - -void AppearanceSettings::setMaxFontSize(int _max) -{ - setValue(_max, "maxFontSize"); -} - QString AppearanceSettings::getHomeTabBackgroundSource() const { return getValue("homeTabBackgroundSource", QString(), QString(), "themed").toString(); diff --git a/libcockatrice_settings/libcockatrice/settings/appearance_settings.h b/libcockatrice_settings/libcockatrice/settings/appearance_settings.h index 9d15caddd..1e0823ae1 100644 --- a/libcockatrice_settings/libcockatrice/settings/appearance_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/appearance_settings.h @@ -24,8 +24,6 @@ public: void setThemeName(const QString &_themeName); [[nodiscard]] bool getStyleUserList() const; void setStyleUserList(bool _styleUserList); - [[nodiscard]] int getMaxFontSize() const; - void setMaxFontSize(int _max); [[nodiscard]] QString getHomeTabBackgroundSource() const; void setHomeTabBackgroundSource(const QString &_backgroundSource); [[nodiscard]] int getHomeTabBackgroundShuffleFrequency() const; diff --git a/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp b/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp index d9652cde3..3be3c0bf2 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_migration.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -395,6 +396,10 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl { const QMap appearanceKeyMap = { {"theme/name", "appearance/themeName"}, + // Retired into the theme's CardText size, but still read here so users + // who have not migrated past global.ini keep their card text size: + // SettingsCache runs migrateSettingsFromGlobalIni() before + // migrateAppearanceMaxFontSizeToTheme(). {"game/maxfontsize", "appearance/maxFontSize"}, {"home/background", "appearance/homeTabBackgroundSource"}, {"home/background/shuffleTimer", "appearance/homeTabBackgroundShuffleFrequency"}, @@ -424,6 +429,181 @@ static void migrateAppearanceSettings(const QString &settingsPath, QSettings &gl } } +static QString themesPathForSettings(const QString &settingsPath) +{ + QSettings pathsIni(settingsPath + "paths.ini", QSettings::IniFormat); + const QString configured = pathsIni.value("paths/themes").toString().trimmed(); + if (!configured.isEmpty()) { + return configured; + } + // Default layout: /themes/ sibling of /settings/. + return QDir::cleanPath(QDir(settingsPath).absoluteFilePath("../themes")); +} + +static bool fontsTomlHasExplicitCardTextMax(const QString &filePath) +{ + QFile f(filePath); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + + QString currentSection; + QTextStream in(&f); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) { + continue; + } + if (line.startsWith('[') && line.endsWith(']')) { + currentSection = line.mid(1, line.length() - 2).trimmed(); + continue; + } + if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) { + continue; + } + int eq = line.indexOf('='); + if (eq < 0) { + continue; + } + QString key = line.left(eq).trimmed(); + QString value = line.mid(eq + 1).trimmed(); + for (int i = 1; i < value.size(); ++i) { + if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) { + value = value.left(i).trimmed(); + break; + } + } + if (value.size() >= 2 && + ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) { + value = value.mid(1, value.size() - 2).trimmed(); + } + if (key.startsWith("AppFont::")) { + key = key.mid(QStringLiteral("AppFont::").size()); + } + if (key.compare("CardTextMaxSize", Qt::CaseInsensitive) == 0) { + return true; + } + if (key.compare("CardText", Qt::CaseInsensitive) != 0) { + continue; + } + int comma = value.lastIndexOf(','); + if (comma < 0) { + continue; + } + QString family = value.left(comma).trimmed(); + if (family.size() >= 2 && + ((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) { + family = family.mid(1, family.size() - 2).trimmed(); + } + bool ok = false; + int pointSize = value.mid(comma + 1).trimmed().toInt(&ok); + if (!ok || family.isEmpty() || pointSize <= 0) { + continue; + } + return true; + } + return false; +} + +static bool seedCardTextMaxIntoThemeDir(const QString &themeDirPath, int maxSize) +{ + if (themeDirPath.isEmpty() || maxSize <= 0) { + return false; + } + QDir dir(themeDirPath); + if (!dir.exists()) { + return false; + } + const QString filePath = dir.absoluteFilePath("fonts.toml"); + if (fontsTomlHasExplicitCardTextMax(filePath)) { + return false; + } + + QString existing; + if (QFile::exists(filePath)) { + QFile in(filePath); + if (!in.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + existing = QString::fromUtf8(in.readAll()); + } + + // Size-only key: seeding the CardText family here would pin it to Serif + // for the whole board (names, player labels, the completer), which is not + // what the retired maxFontSize setting meant. Let the family keep its + // existing resolution and only constrain the size. + const QString entry = QString("%1 = %2\n").arg(QStringLiteral("CardTextMaxSize"), -20).arg(maxSize); + + QString out; + if (existing.isEmpty()) { + out = "[Fonts]\n" + entry + "\n"; + } else if (existing.contains("[Fonts]", Qt::CaseInsensitive)) { + out = existing; + if (!out.endsWith('\n')) { + out += '\n'; + } + out += entry; + } else { + out = existing; + if (!out.endsWith('\n')) { + out += '\n'; + } + out += "\n[Fonts]\n" + entry + "\n"; + } + + QFile f(filePath); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { + return false; + } + QTextStream stream(&f); + stream << out; + return true; +} + +static bool appearanceMaxFontSizeMigrationPending(const QString &settingsPath) +{ + if (!QFile::exists(settingsPath + "appearance.ini")) { + return false; + } + QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat); + return appearanceIni.contains("appearance/maxFontSize"); +} + +bool SettingsMigration::migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath) +{ + if (!appearanceMaxFontSizeMigrationPending(settingsPath)) { + return false; + } + + QSettings appearanceIni(settingsPath + "appearance.ini", QSettings::IniFormat); + // Retired default was 12 in AppearanceSettings::getMaxFontSize; only + // non-default values are seeded, default users rely on the theme fallback. + const int legacy = appearanceIni.value("appearance/maxFontSize").toInt(); + if (legacy != 12 && legacy > 0) { + const QString themesPath = themesPathForSettings(settingsPath); + QDir themesDir(themesPath); + const QStringList themes = themesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); + for (const QString &theme : themes) { + seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(theme), legacy); + } + + // Shipped themes (Fabric, Leather, ...) resolve to the system theme + // directory unless the user has already saved something for them, and + // System/Fusion only ever exist in the user directory. Seeding existing + // folders alone would therefore drop the retired size for every user on + // a stock theme, so create the active theme's user directory the same + // way ThemeManager::writableThemeDir() does for palette and font saves. + const QString activeTheme = appearanceIni.value("appearance/themeName").toString().trimmed(); + if (!activeTheme.isEmpty() && !themesDir.exists(activeTheme) && themesDir.mkpath(activeTheme)) { + seedCardTextMaxIntoThemeDir(themesDir.absoluteFilePath(activeTheme), legacy); + } + } + + appearanceIni.remove("appearance/maxFontSize"); + appearanceIni.sync(); + return true; +} + static void migrateNetworkSettings(const QString &settingsPath, QSettings &globalIni) { const QMap networkKeyMap = { @@ -821,6 +1001,10 @@ bool SettingsMigration::isMigrationPending(const QString &settingsPath) return true; } + if (appearanceMaxFontSizeMigrationPending(settingsPath)) { + return true; + } + QSettings personalIni(settingsPath + "personal.ini", QSettings::IniFormat); if (personalIni.value("migration/legacy_complete", false).toBool()) { return false; diff --git a/libcockatrice_settings/libcockatrice/settings/settings_migration.h b/libcockatrice_settings/libcockatrice/settings/settings_migration.h index 0a706af98..7616cb83f 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_migration.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_migration.h @@ -18,6 +18,15 @@ public: */ static bool migrateGameFiltersToCamelCase(const QString &settingsPath); + /** + * Moves a non-default appearance/maxFontSize value into each user-writable theme + * directory lacking an explicit CardText max (creating/merging fonts.toml, never + * overwriting an explicit max), then deletes the legacy key. Default (12) or absent + * values only delete the key and create no files, since the ThemeManager fallback + * already covers them. Idempotent, safe to run on every startup. + */ + static bool migrateAppearanceMaxFontSizeToTheme(const QString &settingsPath); + /** * Returns true when at least one of the two migrations would still do work, i.e. when a * backup of the settings directory is worth taking before they run. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6acd225c2..39a3cbe97 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -412,6 +412,15 @@ cockatrice_add_test( INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/cockatrice/src ) +cockatrice_add_test( + NAME + font_config_test + SOURCES + ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/theme_config.cpp + font_config_test.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/cockatrice/src +) cockatrice_add_test( NAME deck_hash_performance_test diff --git a/tests/font_config_test.cpp b/tests/font_config_test.cpp new file mode 100644 index 000000000..93c9cefb6 --- /dev/null +++ b/tests/font_config_test.cpp @@ -0,0 +1,344 @@ +#include "interface/theme_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +// Writes content to a file inside tmp, creating parents as needed. +QString writeTempFile(QTemporaryDir &tmp, const QString &relativePath, const QString &content) +{ + const QString filePath = QDir(tmp.path()).absoluteFilePath(relativePath); + EXPECT_TRUE(QDir().mkpath(QFileInfo(filePath).absolutePath())); + QFile file(filePath); + EXPECT_TRUE(file.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&file); + out << content; + file.close(); + return filePath; +} + +QFont makeFont(const QString &family, int pointSize) +{ + QFont font(family); + font.setPointSize(pointSize); + return font; +} +} // namespace + +TEST(FontConfigTest, FileNameIsFontsToml) +{ + EXPECT_EQ(FontConfig::fileName(), QStringLiteral("fonts.toml")); +} + +TEST(FontConfigTest, EmptyByDefault) +{ + FontConfig cfg; + EXPECT_FALSE(cfg.hasFonts()); + EXPECT_TRUE(cfg.toToml().isEmpty()); + EXPECT_EQ(cfg, FontConfig()); + EXPECT_EQ(cfg, FontConfig::fromFile("/nonexistent/path/fonts.toml")); + EXPECT_EQ(cfg, FontConfig::fromThemeDir(QString())); +} + +TEST(FontConfigTest, RoundTripPreservesFamilyAndSize) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + FontConfig cfg; + cfg.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); + cfg.fonts[AppFont::ChatLog] = makeFont("JetBrains Mono", 9); + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 11); + cfg.fonts[AppFont::DeckList] = makeFont("Roboto", 12); + ASSERT_TRUE(cfg.hasFonts()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + EXPECT_EQ(loaded, cfg); + EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault].family(), QStringLiteral("Inter")); + EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault].pointSize(), 10); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog].family(), QStringLiteral("JetBrains Mono")); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog].pointSize(), 9); + EXPECT_EQ(loaded.fonts[AppFont::CardText].family(), QStringLiteral("Noto Serif")); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 11); + EXPECT_EQ(loaded.fonts[AppFont::DeckList].family(), QStringLiteral("Roboto")); + EXPECT_EQ(loaded.fonts[AppFont::DeckList].pointSize(), 12); +} + +TEST(FontConfigTest, RoundTripStripsWeightAndItalic) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + QFont styled("Inter"); + styled.setPointSize(10); + styled.setBold(true); + styled.setItalic(true); + + FontConfig cfg; + cfg.fonts[AppFont::ChatLog] = styled; + + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + ASSERT_TRUE(loaded.fonts.contains(AppFont::ChatLog)); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog].family(), QStringLiteral("Inter")); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog].pointSize(), 10); + EXPECT_FALSE(loaded.fonts[AppFont::ChatLog].bold()); + EXPECT_FALSE(loaded.fonts[AppFont::ChatLog].italic()); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog], makeFont("Inter", 10)); +} + +TEST(FontConfigTest, ToTomlFormat) +{ + FontConfig cfg; + cfg.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); + + const QString toml = cfg.toToml(); + EXPECT_TRUE(toml.contains(QStringLiteral("[Fonts]"))); + EXPECT_TRUE(toml.contains(QStringLiteral("InterfaceDefault"))); + EXPECT_TRUE(toml.contains(QStringLiteral("\"Inter, 10\""))); +} + +TEST(FontConfigTest, MalformedLinesAreSkipped) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", + "# a comment\n" + "; another comment\n" + "\n" + "StrayBeforeSection = \"Inter, 10\"\n" + "\n" + "[Other]\n" + "ChatLog = \"ShouldBeIgnored, 10\"\n" + "\n" + "[Fonts]\n" + "InterfaceDefault = \"Inter, 10\"\n" + "ChatLog = \"JetBrains Mono\", 9 # inline comment\n" + "CardText = Noto Serif, 11\n" + "AppFont::DeckList = \"Roboto, 12\"\n" + "UnknownRole = \"Inter, 10\"\n" + "QPalette::Window = \"Inter, 10\"\n" + "NoComma = \"Inter\"\n" + "EmptyFamily = \", 10\"\n" + "ZeroSize = \"Inter, 0\"\n" + "NegativeSize = \"Inter, -3\"\n" + "NotANumber = \"Inter, big\"\n" + "NotAnEntry\n"); + + const FontConfig loaded = FontConfig::fromFile(filePath); + + ASSERT_TRUE(loaded.hasFonts()); + EXPECT_EQ(loaded.fonts.size(), 4); + EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault], makeFont("Inter", 10)); + EXPECT_EQ(loaded.fonts[AppFont::ChatLog], makeFont("JetBrains Mono", 9)); + EXPECT_EQ(loaded.fonts[AppFont::CardText], makeFont("Noto Serif", 11)); + EXPECT_EQ(loaded.fonts[AppFont::DeckList], makeFont("Roboto", 12)); +} + +TEST(FontConfigTest, FromThemeDirReadsFontsToml) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + const QString themeDir = QDir(tmp.path()).absoluteFilePath("MyTheme"); + ASSERT_TRUE(QDir().mkpath(themeDir)); + + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 11); + writeTempFile(tmp, "MyTheme/fonts.toml", cfg.toToml()); + + const FontConfig loaded = FontConfig::fromThemeDir(themeDir); + EXPECT_EQ(loaded, cfg); + + // A theme directory without fonts.toml loads as empty. + const QString bareDir = QDir(tmp.path()).absoluteFilePath("BareTheme"); + ASSERT_TRUE(QDir().mkpath(bareDir)); + EXPECT_FALSE(FontConfig::fromThemeDir(bareDir).hasFonts()); +} + +TEST(FontConfigTest, InequalityComparesRoles) +{ + FontConfig left; + left.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10); + + FontConfig right = left; + EXPECT_EQ(left, right); + EXPECT_FALSE(left != right); + + right.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 11); + EXPECT_NE(left, right); + + right = left; + right.fonts[AppFont::ChatLog] = makeFont("Inter", 10); + EXPECT_NE(left, right); +} + +TEST(FontConfigTest, CardTextMinRoundTrip) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 14); + cfg.cardTextMinSize = 3; + ASSERT_TRUE(cfg.hasFonts()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + EXPECT_EQ(loaded, cfg); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14); + EXPECT_EQ(loaded.cardTextMinSize, 3); +} + +TEST(FontConfigTest, CardTextMinDefaultIsOmitted) +{ + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 12); + EXPECT_EQ(cfg.cardTextMinSize, 1); + + const QString toml = cfg.toToml(); + EXPECT_TRUE(toml.contains(QStringLiteral("CardText"))); + EXPECT_FALSE(toml.contains(QStringLiteral("CardTextMinSize"))); + + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + const QString filePath = writeTempFile(tmp, "fonts.toml", toml); + EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMinSize, 1); +} + +TEST(FontConfigTest, MalformedMinIsSkipped) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", + "[Fonts]\n" + "CardText = \"Noto Serif, 14\"\n" + "CardTextMinSize = big\n"); + + // Each malformed value parses independently, so rewrite the file per case. + const QStringList badValues = {"big", "0", "-3", "", "Inter, 10"}; + for (const QString &bad : badValues) { + const QString badPath = writeTempFile( + tmp, "bad.toml", QString("[Fonts]\nCardText = \"Noto Serif, 14\"\nCardTextMinSize = %1\n").arg(bad)); + const FontConfig loaded = FontConfig::fromFile(badPath); + EXPECT_EQ(loaded.cardTextMinSize, 1) << "bad value: " << bad.toStdString(); + ASSERT_TRUE(loaded.fonts.contains(AppFont::CardText)); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14); + } + + // The valid file still loads. + EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMinSize, 1); +} + +TEST(FontConfigTest, CardTextMaxRoundTrip) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 14); + cfg.cardTextMaxSize = 18; + ASSERT_TRUE(cfg.hasFonts()); + + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + EXPECT_EQ(loaded, cfg); + EXPECT_EQ(loaded.cardTextMaxSize, 18); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14); +} + +TEST(FontConfigTest, CardTextMaxSizeWithoutFamily) +{ + // The retired maxFontSize setting migrates to a size-only key, so the + // family keeps its existing resolution instead of being pinned to Serif. + FontConfig cfg; + cfg.cardTextMaxSize = 18; + ASSERT_TRUE(cfg.hasFonts()); + + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml()); + const FontConfig loaded = FontConfig::fromFile(filePath); + + EXPECT_TRUE(loaded.fonts.isEmpty()); + EXPECT_EQ(loaded.cardTextMaxSize, 18); + EXPECT_EQ(loaded, cfg); +} + +TEST(FontConfigTest, CardTextMaxDefaultIsOmitted) +{ + FontConfig cfg; + cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 12); + EXPECT_EQ(cfg.cardTextMaxSize, 0); + + const QString toml = cfg.toToml(); + EXPECT_FALSE(toml.contains(QStringLiteral("CardTextMaxSize"))); + + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + const QString filePath = writeTempFile(tmp, "fonts.toml", toml); + EXPECT_EQ(FontConfig::fromFile(filePath).cardTextMaxSize, 0); +} + +TEST(FontConfigTest, MalformedMaxIsSkipped) +{ + QTemporaryDir tmp; + ASSERT_TRUE(tmp.isValid()); + + const QStringList badValues = {"big", "0", "-3", "", "Inter, 18"}; + for (const QString &bad : badValues) { + const QString badPath = writeTempFile( + tmp, "bad.toml", QString("[Fonts]\nCardText = \"Noto Serif, 14\"\nCardTextMaxSize = %1\n").arg(bad)); + const FontConfig loaded = FontConfig::fromFile(badPath); + EXPECT_EQ(loaded.cardTextMaxSize, 0) << "bad value: " << bad.toStdString(); + ASSERT_TRUE(loaded.fonts.contains(AppFont::CardText)); + EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 14); + } +} + +TEST(FontConfigTest, InequalityComparesMinSize) +{ + FontConfig left; + left.fonts[AppFont::CardText] = makeFont("Noto Serif", 12); + left.cardTextMinSize = 1; + + FontConfig right = left; + right.cardTextMinSize = 2; + EXPECT_NE(left, right); +} + +TEST(FontConfigTest, InequalityComparesMaxSize) +{ + FontConfig left; + left.fonts[AppFont::CardText] = makeFont("Noto Serif", 12); + left.cardTextMaxSize = 12; + + FontConfig right = left; + right.cardTextMaxSize = 18; + EXPECT_NE(left, right); +} + +// Every test brings its own main(): GTest is not guaranteed to be found on the build machines, and +// the fallback then links the bare gtest library without gtest_main. +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index aea7f1d54..156e52695 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -461,19 +461,6 @@ TEST_F(SettingsDefaultsTest, Appearance_StyleUserList_Default) ASSERT_EQ(s.getStyleUserList(), true); } -TEST_F(SettingsDefaultsTest, Appearance_MaxFontSize_Default) -{ - AppearanceSettings s(settingsPath, nullptr); - ASSERT_EQ(s.getMaxFontSize(), 12); -} - -TEST_F(SettingsDefaultsTest, Appearance_MaxFontSize_SetAndGet) -{ - AppearanceSettings s(settingsPath, nullptr); - s.setMaxFontSize(14); - ASSERT_EQ(s.getMaxFontSize(), 14); -} - TEST_F(SettingsDefaultsTest, Appearance_HomeTabBackgroundSource_Default) { AppearanceSettings s(settingsPath, nullptr); diff --git a/tests/settings/settings_migration_test.cpp b/tests/settings/settings_migration_test.cpp index 2c4dcf7a9..4fea3c085 100644 --- a/tests/settings/settings_migration_test.cpp +++ b/tests/settings/settings_migration_test.cpp @@ -984,6 +984,198 @@ TEST_F(SettingsMigrationTest, IsNotMigrationPendingWithoutLegacyData) ASSERT_FALSE(SettingsMigration::isMigrationPending(settingsPath)); } +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_NonDefaultSeedsThemeAndDeletesKey) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme")); + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid()); + + const QString fontsPath = themesPath + "/MyTheme/fonts.toml"; + ASSERT_TRUE(QFile::exists(fontsPath)); + QFile f(fontsPath); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("CardTextMaxSize")); + EXPECT_TRUE(content.contains("18")); + // Size-only: the migration must not pin the family, or every CardText + // surface switches to Serif for users who never chose it. + EXPECT_FALSE(content.contains("Serif")); + + ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_SeedsActiveStockTheme) +{ + // A stock theme resolves to the system directory, so there is no user theme + // folder to seed. The migration has to create the active theme's folder, + // otherwise the retired size is deleted without landing anywhere. + const QString themesPath = tempDir.path() + "/themes"; + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/themeName", "Fabric"); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid()); + + const QString fontsPath = themesPath + "/Fabric/fonts.toml"; + ASSERT_TRUE(QFile::exists(fontsPath)); + QFile f(fontsPath); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("CardTextMaxSize")); + EXPECT_TRUE(content.contains("18")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_KeepsExistingActiveTheme) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/Fabric")); + { + QFile f(themesPath + "/Fabric/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&f); + out << "[Fonts]\nCardText = \"Inter, 10\"\n"; + } + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/themeName", "Fabric"); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + QFile f(themesPath + "/Fabric/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("Inter, 10")); + EXPECT_FALSE(content.contains("18")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DefaultDeletesKeyWithoutFiles) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme")); + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 12); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid()); + ASSERT_FALSE(QFile::exists(themesPath + "/MyTheme/fonts.toml")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DoesNotOverwriteExplicitMax) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme")); + { + QFile f(themesPath + "/MyTheme/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&f); + out << "[Fonts]\nCardText = \"Inter, 10\"\n"; + } + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + QFile f(themesPath + "/MyTheme/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("10")); + EXPECT_FALSE(content.contains("18")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_MergesWithOtherRoles) +{ + const QString themesPath = tempDir.path() + "/themes"; + ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme")); + { + QFile f(themesPath + "/MyTheme/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&f); + out << "[Fonts]\nInterfaceDefault = \"Inter, 10\"\n"; + } + { + QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat); + paths.setValue("paths/themes", themesPath); + paths.sync(); + } + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 20); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); + + QFile f(themesPath + "/MyTheme/fonts.toml"); + ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text)); + const QString content = QString::fromUtf8(f.readAll()); + EXPECT_TRUE(content.contains("InterfaceDefault")); + EXPECT_TRUE(content.contains("CardTextMaxSize")); + EXPECT_TRUE(content.contains("20")); +} + +TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_AbsentKeyDoesNothing) +{ + ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath)); +} + +TEST_F(SettingsMigrationTest, IsMigrationPendingWhenMaxFontSizePresent) +{ + { + QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat); + appearance.setValue("appearance/maxFontSize", 18); + appearance.sync(); + } + + ASSERT_TRUE(SettingsMigration::isMigrationPending(settingsPath)); +} + } // namespace int main(int argc, char **argv)