mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
Compare commits
5 commits
15655767a8
...
7472f69f01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7472f69f01 | ||
|
|
c0aeaf72e8 | ||
|
|
c63a359c3d | ||
|
|
58ee72543a | ||
|
|
1385b69bac |
11 changed files with 809 additions and 13 deletions
|
|
@ -172,6 +172,7 @@ set(cockatrice_SOURCES
|
|||
src/interface/palette_editor/palette_generator.cpp
|
||||
src/interface/palette_editor/quick_setup_panel.cpp
|
||||
src/interface/palette_editor/palette_grid_widget.cpp
|
||||
src/interface/palette_editor/font_grid_widget.cpp
|
||||
src/interface/palette_editor/palette_editor_dialog.cpp
|
||||
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
|
||||
src/interface/widgets/cards/additional_info/deck_color_identity.cpp
|
||||
|
|
|
|||
127
cockatrice/src/interface/palette_editor/font_grid_widget.cpp
Normal file
127
cockatrice/src/interface/palette_editor/font_grid_widget.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#include "font_grid_widget.h"
|
||||
|
||||
#include "../theme_manager.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFont>
|
||||
#include <QFontComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QMetaEnum>
|
||||
#include <QSpinBox>
|
||||
#include <QtGlobal>
|
||||
|
||||
static constexpr int MIN_FONT_SIZE = 6;
|
||||
static constexpr int MAX_FONT_SIZE = 72;
|
||||
static constexpr int FALLBACK_FONT_SIZE = 10;
|
||||
|
||||
static const QMap<AppFont::Role, const char *> APP_FONT_DESCRIPTIONS = {
|
||||
{AppFont::InterfaceDefault, QT_TR_NOOP("Default font for menus, dialogs and widgets")},
|
||||
{AppFont::ChatLog, QT_TR_NOOP("Font for chat messages and logs")},
|
||||
{AppFont::CardText, QT_TR_NOOP("Font for text rendered on cards")},
|
||||
{AppFont::DeckList, QT_TR_NOOP("Font for deck lists and the deck editor")},
|
||||
};
|
||||
|
||||
static QString roleDisplayName(AppFont::Role role)
|
||||
{
|
||||
switch (role) {
|
||||
case AppFont::InterfaceDefault:
|
||||
return FontGridWidget::tr("Interface default");
|
||||
case AppFont::ChatLog:
|
||||
return FontGridWidget::tr("Chat log");
|
||||
case AppFont::CardText:
|
||||
return FontGridWidget::tr("Card text");
|
||||
case AppFont::DeckList:
|
||||
return FontGridWidget::tr("Deck list");
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
auto *grid = new QGridLayout(this);
|
||||
grid->setSpacing(3);
|
||||
grid->setContentsMargins(12, 8, 12, 8);
|
||||
grid->setColumnStretch(0, 0);
|
||||
grid->setColumnStretch(1, 1);
|
||||
grid->setColumnStretch(2, 0);
|
||||
|
||||
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
|
||||
for (int i = 0; i < fontEnum.keyCount(); ++i) {
|
||||
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
|
||||
|
||||
auto *label = new QLabel(this);
|
||||
grid->addWidget(label, i, 0);
|
||||
|
||||
auto *familyCombo = new QFontComboBox(this);
|
||||
grid->addWidget(familyCombo, i, 1);
|
||||
connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); });
|
||||
|
||||
auto *sizeSpin = new QSpinBox(this);
|
||||
sizeSpin->setRange(MIN_FONT_SIZE, MAX_FONT_SIZE);
|
||||
grid->addWidget(sizeSpin, i, 2);
|
||||
connect(sizeSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
|
||||
|
||||
fontRows[role] = {label, familyCombo, sizeSpin};
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void FontGridWidget::loadFonts(const FontConfig &cfg)
|
||||
{
|
||||
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
|
||||
for (int i = 0; i < fontEnum.keyCount(); ++i) {
|
||||
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
|
||||
const FontRow &row = fontRows[role];
|
||||
|
||||
// Roles absent from the file resolve the same way the runtime does: an unset
|
||||
// role falls back to the config's InterfaceDefault, and only then to the
|
||||
// startup font. appFont() would return the live preview instead, so Reset and
|
||||
// a first load must not measure against it.
|
||||
const QFont fallback = cfg.fonts.value(AppFont::InterfaceDefault, themeManager->defaultAppFont(role));
|
||||
const QFont font = cfg.fonts.value(role, fallback);
|
||||
row.familyCombo->setCurrentFont(font);
|
||||
|
||||
int size = font.pointSize();
|
||||
if (size <= 0) {
|
||||
size = qApp->font().pointSize();
|
||||
}
|
||||
if (size <= 0) {
|
||||
size = FALLBACK_FONT_SIZE;
|
||||
}
|
||||
row.sizeSpin->setValue(qBound(MIN_FONT_SIZE, size, MAX_FONT_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
FontConfig FontGridWidget::currentFontConfig() const
|
||||
{
|
||||
FontConfig cfg;
|
||||
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
|
||||
for (int i = 0; i < fontEnum.keyCount(); ++i) {
|
||||
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
|
||||
const FontRow &row = fontRows.value(role);
|
||||
|
||||
// Canonical family + point size only: a fresh QFont carries default
|
||||
// (non-bold, non-italic) weight/style, so nothing combo-side leaks in.
|
||||
QFont font(row.familyCombo->currentFont().family());
|
||||
font.setPointSize(row.sizeSpin->value());
|
||||
cfg.fonts[role] = font;
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
void FontGridWidget::retranslateUi()
|
||||
{
|
||||
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
|
||||
for (int i = 0; i < fontEnum.keyCount(); ++i) {
|
||||
auto role = static_cast<AppFont::Role>(fontEnum.value(i));
|
||||
const FontRow &row = fontRows.value(role);
|
||||
|
||||
row.label->setText(roleDisplayName(role));
|
||||
row.label->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, "")));
|
||||
row.familyCombo->setToolTip(tr(APP_FONT_DESCRIPTIONS.value(role, "")));
|
||||
row.sizeSpin->setToolTip(tr("Point size"));
|
||||
}
|
||||
}
|
||||
43
cockatrice/src/interface/palette_editor/font_grid_widget.h
Normal file
43
cockatrice/src/interface/palette_editor/font_grid_widget.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef COCKATRICE_FONT_GRID_WIDGET_H
|
||||
#define COCKATRICE_FONT_GRID_WIDGET_H
|
||||
|
||||
#include "../theme_config.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QWidget>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class QFontComboBox;
|
||||
class QGridLayout;
|
||||
class QLabel;
|
||||
class QSpinBox;
|
||||
|
||||
// Per-role font editors — one (family, point size) row per AppFont::Role.
|
||||
// Owns the load/read round-trip for FontConfig but has no file I/O, mirroring
|
||||
// PaletteGridWidget. Only family and point size are edited; weight and italic
|
||||
// stay caller-side concerns and are stripped when reading back.
|
||||
class FontGridWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FontGridWidget(QWidget *parent = nullptr);
|
||||
|
||||
void loadFonts(const FontConfig &cfg);
|
||||
FontConfig currentFontConfig() const;
|
||||
void retranslateUi();
|
||||
|
||||
signals:
|
||||
void fontsChanged();
|
||||
|
||||
private:
|
||||
struct FontRow
|
||||
{
|
||||
QLabel *label = nullptr;
|
||||
QFontComboBox *familyCombo = nullptr;
|
||||
QSpinBox *sizeSpin = nullptr;
|
||||
};
|
||||
|
||||
QMap<AppFont::Role, FontRow> fontRows;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_FONT_GRID_WIDGET_H
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "../theme_config.h"
|
||||
#include "../theme_manager.h"
|
||||
#include "font_grid_widget.h"
|
||||
#include "palette_generator.h"
|
||||
#include "palette_grid_widget.h"
|
||||
#include "quick_setup_panel.h"
|
||||
|
|
@ -40,6 +41,7 @@ PaletteEditorDialog::PaletteEditorDialog(const QString &_themeDirPath, const QSt
|
|||
|
||||
// Load both scheme configs upfront so switching is instant
|
||||
loadSchemes();
|
||||
loadFonts();
|
||||
|
||||
loadedScheme = themeManager->isDarkMode(themeDirPath) ? "Dark" : "Light";
|
||||
|
||||
|
|
@ -126,6 +128,32 @@ void PaletteEditorDialog::setupUi()
|
|||
paletteGrid->setVisible(false);
|
||||
root->addWidget(paletteGrid, 1);
|
||||
|
||||
// Font section — sibling to the palette grid, sharing its collapsible
|
||||
// advanced area pattern. Fonts are shared per theme, not split by scheme.
|
||||
fontToggleButton = new QPushButton(this);
|
||||
fontToggleButton->setCheckable(true);
|
||||
fontToggleButton->setChecked(false);
|
||||
fontToggleButton->setFlat(true);
|
||||
fontToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }"
|
||||
"QPushButton:checked { }");
|
||||
root->addWidget(fontToggleButton);
|
||||
|
||||
fontSeparator = makeSeparator();
|
||||
fontSeparator->setVisible(false);
|
||||
root->addWidget(fontSeparator);
|
||||
|
||||
fontSection = new QWidget;
|
||||
auto *fontSectionLayout = new QVBoxLayout(fontSection);
|
||||
fontSectionLayout->setContentsMargins(0, 0, 0, 0);
|
||||
fontSectionLayout->setSpacing(0);
|
||||
fontNoteLabel = new QLabel(fontSection);
|
||||
fontNoteLabel->setContentsMargins(12, 4, 12, 0);
|
||||
fontSectionLayout->addWidget(fontNoteLabel);
|
||||
fontGrid = new FontGridWidget(fontSection);
|
||||
fontSectionLayout->addWidget(fontGrid);
|
||||
fontSection->setVisible(false);
|
||||
root->addWidget(fontSection);
|
||||
|
||||
// Footer
|
||||
root->addWidget(makeSeparator());
|
||||
footer = new QWidget;
|
||||
|
|
@ -158,6 +186,7 @@ void PaletteEditorDialog::setupUi()
|
|||
connect(autoApplyTimer, &QTimer::timeout, this, &PaletteEditorDialog::onApply);
|
||||
connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &PaletteEditorDialog::onGenerateFromAccent);
|
||||
connect(paletteGrid, &PaletteGridWidget::paletteChanged, this, [this] { autoApplyTimer->start(); });
|
||||
connect(fontGrid, &FontGridWidget::fontsChanged, this, [this] { autoApplyTimer->start(); });
|
||||
connect(revertButton, &QPushButton::clicked, this, &PaletteEditorDialog::onRevertToDefault);
|
||||
connect(resetBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onReset);
|
||||
connect(saveBtn, &QPushButton::clicked, this, &PaletteEditorDialog::onSave);
|
||||
|
|
@ -167,15 +196,32 @@ void PaletteEditorDialog::setupUi()
|
|||
paletteGridToggleButton->setText(open ? tr("▼ Edit Palette") : tr("▶ Edit Palette"));
|
||||
paletteGridSeparator->setVisible(open);
|
||||
paletteGrid->setVisible(open);
|
||||
|
||||
if (open) {
|
||||
setMinimumHeight(680);
|
||||
resize(width(), 680);
|
||||
} else {
|
||||
setMinimumHeight(220);
|
||||
adjustSize(); // shrinks to fit just the visible content
|
||||
}
|
||||
updateAdvancedHeight();
|
||||
});
|
||||
connect(fontToggleButton, &QPushButton::toggled, this, [this](bool open) {
|
||||
fontToggleButton->setText(open ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts"));
|
||||
fontSeparator->setVisible(open);
|
||||
fontSection->setVisible(open);
|
||||
updateAdvancedHeight();
|
||||
});
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::updateAdvancedHeight()
|
||||
{
|
||||
int height = 220;
|
||||
if (paletteGridToggleButton->isChecked()) {
|
||||
height += 460;
|
||||
}
|
||||
if (fontToggleButton->isChecked()) {
|
||||
height += 200;
|
||||
}
|
||||
setMinimumHeight(height);
|
||||
|
||||
if (paletteGrid->isVisible() || fontSection->isVisible()) {
|
||||
resize(width(), height);
|
||||
} else {
|
||||
adjustSize(); // shrinks to fit just the visible content
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::retranslateUi()
|
||||
|
|
@ -190,12 +236,16 @@ void PaletteEditorDialog::retranslateUi()
|
|||
editingLabel->setText(tr("Editing:"));
|
||||
paletteGridToggleButton->setText(tr("▶ Edit Palette"));
|
||||
paletteGridToggleButton->setToolTip(tr("Show or hide the per-role colour grid for manual tweaks"));
|
||||
fontToggleButton->setText(fontSection->isVisible() ? tr("▼ Edit Fonts") : tr("▶ Edit Fonts"));
|
||||
fontToggleButton->setToolTip(tr("Show or hide the per-purpose font settings for this theme"));
|
||||
fontNoteLabel->setText(tr("Fonts belong to theme %1").arg(themeName));
|
||||
fontGrid->retranslateUi();
|
||||
revertButton->setText(tr("↺ Revert to theme default"));
|
||||
|
||||
resetBtn->setText(tr("Reset"));
|
||||
saveBtn->setText(tr("Save"));
|
||||
resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette"));
|
||||
saveBtn->setToolTip(tr("Write palette-%1.toml and reload the theme").arg(loadedScheme.toLower()));
|
||||
resetBtn->setToolTip(tr("Discard unsaved edits and restore the last saved palette and fonts"));
|
||||
saveBtn->setToolTip(tr("Write palette-%1.toml and fonts.toml, then reload the theme").arg(loadedScheme.toLower()));
|
||||
|
||||
if (saveDir.isEmpty() || !ThemeManager::isDirReallyWritable(saveDir)) {
|
||||
saveBtn->setEnabled(false);
|
||||
|
|
@ -229,6 +279,19 @@ void PaletteEditorDialog::loadSchemes()
|
|||
}
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::loadFonts()
|
||||
{
|
||||
// Same user-dir > system-dir fallback ThemeManager uses: the shipped
|
||||
// fonts.toml may live in the system theme directory rather than the
|
||||
// resolved (user) theme directory.
|
||||
FontConfig cfg = ThemeManager::loadFontConfig(themeDirPath);
|
||||
if (!cfg.hasFonts()) {
|
||||
cfg = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName);
|
||||
}
|
||||
savedFontConfig = cfg;
|
||||
fontGrid->loadFonts(cfg);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::seedAccentFromScheme(const QString &scheme)
|
||||
{
|
||||
QColor seed = workingConfig.value(scheme).colors.value(QPalette::Active).value(QPalette::Highlight);
|
||||
|
|
@ -259,6 +322,7 @@ void PaletteEditorDialog::onGenerateFromAccent(const QColor &accent, int intensi
|
|||
void PaletteEditorDialog::onApply()
|
||||
{
|
||||
themeManager->previewPalette(paletteGrid->currentPaletteConfig(), loadedScheme);
|
||||
themeManager->previewFonts(fontGrid->currentFontConfig());
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::onSave()
|
||||
|
|
@ -309,6 +373,17 @@ void PaletteEditorDialog::onSave()
|
|||
savedConfig[it.key()] = it.value();
|
||||
}
|
||||
|
||||
// Persist the shared per-theme fonts when they changed.
|
||||
const FontConfig fontCfg = fontGrid->currentFontConfig();
|
||||
if (fontCfg != savedFontConfig) {
|
||||
if (!ThemeManager::saveFontConfig(saveDir, fontCfg)) {
|
||||
QMessageBox::warning(this, tr("Save failed"),
|
||||
tr("Could not write %1 to:\n%2").arg(FontConfig::fileName(), saveDir));
|
||||
return;
|
||||
}
|
||||
savedFontConfig = fontCfg;
|
||||
}
|
||||
|
||||
themeManager->reloadCurrentTheme();
|
||||
accept();
|
||||
}
|
||||
|
|
@ -317,6 +392,11 @@ void PaletteEditorDialog::onReset()
|
|||
{
|
||||
workingConfig[loadedScheme] = savedConfig[loadedScheme];
|
||||
paletteGrid->loadPalette(savedConfig[loadedScheme]);
|
||||
// Restore the live theme before re-reading the grid: loadFonts resolves
|
||||
// unset roles, and the preview has currentFonts pointing at the edited
|
||||
// config until the theme is reloaded.
|
||||
themeManager->reloadCurrentTheme();
|
||||
fontGrid->loadFonts(savedFontConfig);
|
||||
seedAccentFromScheme(loadedScheme);
|
||||
}
|
||||
|
||||
|
|
@ -351,6 +431,18 @@ void PaletteEditorDialog::onRevertToDefault()
|
|||
workingConfig[loadedScheme] = def;
|
||||
paletteGrid->loadPalette(def);
|
||||
seedAccentFromScheme(loadedScheme);
|
||||
|
||||
// Revert the shared per-theme fonts the same way: delete the user-written
|
||||
// fonts.toml so the theme falls back to its shipped default, then reflect
|
||||
// the resolved fonts in the editor.
|
||||
QFile::remove(QDir(saveDir).absoluteFilePath(FontConfig::fileName()));
|
||||
|
||||
// Reload the live theme so the font revert takes effect immediately.
|
||||
themeManager->reloadCurrentTheme();
|
||||
|
||||
const FontConfig fontDef = ThemeManager::loadDefaultFontConfig(themeDirPath, themeName);
|
||||
savedFontConfig = fontDef;
|
||||
fontGrid->loadFonts(fontDef);
|
||||
}
|
||||
|
||||
void PaletteEditorDialog::changeEvent(QEvent *e)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class QComboBox;
|
|||
class QDialogButtonBox;
|
||||
class QPushButton;
|
||||
class PaletteGridWidget;
|
||||
class FontGridWidget;
|
||||
class QuickSetupPanel;
|
||||
class QFrame;
|
||||
class QWidget;
|
||||
|
|
@ -38,8 +39,10 @@ private:
|
|||
void setupUi();
|
||||
void retranslateUi();
|
||||
void refreshChromePalettes();
|
||||
void updateAdvancedHeight();
|
||||
void loadScheme(const QString &scheme); // snapshot current, switch, load
|
||||
void seedAccentFromScheme(const QString &scheme);
|
||||
void loadFonts(); // shared per theme, not split by light/dark
|
||||
|
||||
// Sub-widgets
|
||||
QWidget *header;
|
||||
|
|
@ -49,6 +52,11 @@ private:
|
|||
PaletteGridWidget *paletteGrid = nullptr;
|
||||
QPushButton *paletteGridToggleButton = nullptr;
|
||||
QFrame *paletteGridSeparator = nullptr;
|
||||
FontGridWidget *fontGrid = nullptr;
|
||||
QPushButton *fontToggleButton = nullptr;
|
||||
QFrame *fontSeparator = nullptr;
|
||||
QWidget *fontSection = nullptr;
|
||||
QLabel *fontNoteLabel = nullptr;
|
||||
QWidget *footer;
|
||||
QComboBox *schemeComboBox = nullptr;
|
||||
QDialogButtonBox *buttonBox = nullptr;
|
||||
|
|
@ -68,6 +76,10 @@ private:
|
|||
QMap<QString, PaletteConfig> workingConfig;
|
||||
QMap<QString, PaletteConfig> savedConfig;
|
||||
|
||||
// Fonts are shared per theme (fonts.toml, not split by light/dark), so a
|
||||
// single saved config suffices; the live edits are read from fontGrid.
|
||||
FontConfig savedFontConfig;
|
||||
|
||||
QTimer *autoApplyTimer = nullptr;
|
||||
|
||||
protected:
|
||||
|
|
|
|||
|
|
@ -304,4 +304,150 @@ QPalette PaletteConfig::apply(QPalette base) const
|
|||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
bool FontConfig::hasFonts() const
|
||||
{
|
||||
return !fonts.isEmpty();
|
||||
}
|
||||
|
||||
QString FontConfig::toToml() const
|
||||
{
|
||||
QMetaEnum roleEnum = QMetaEnum::fromType<AppFont::Role>();
|
||||
|
||||
QString entries;
|
||||
|
||||
for (auto it = fonts.cbegin(); it != fonts.cend(); ++it) {
|
||||
const char *roleName = roleEnum.valueToKey(it.key());
|
||||
|
||||
if (!roleName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString family = it.value().family().trimmed();
|
||||
int pointSize = it.value().pointSize();
|
||||
|
||||
if (family.isEmpty() || pointSize <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize);
|
||||
}
|
||||
|
||||
if (entries.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return "[Fonts]\n" + entries + "\n";
|
||||
}
|
||||
|
||||
QString FontConfig::fileName()
|
||||
{
|
||||
return "fonts.toml";
|
||||
}
|
||||
|
||||
FontConfig FontConfig::fromFile(const QString &filePath)
|
||||
{
|
||||
FontConfig cfg;
|
||||
|
||||
QFile f(filePath);
|
||||
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
QMetaEnum roleEnum = QMetaEnum::fromType<AppFont::Role>();
|
||||
|
||||
QString currentSection;
|
||||
|
||||
QTextStream in(&f);
|
||||
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
|
||||
if (line.isEmpty() || line.startsWith('#') || line.startsWith(';')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('[') && line.endsWith(']')) {
|
||||
currentSection = line.mid(1, line.length() - 2).trimmed();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentSection.compare("Fonts", Qt::CaseInsensitive) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int eq = line.indexOf('=');
|
||||
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString key = line.left(eq).trimmed();
|
||||
QString value = line.mid(eq + 1).trimmed();
|
||||
|
||||
// Strip inline comments if preceded by whitespace
|
||||
for (int i = 1; i < value.size(); ++i) {
|
||||
if ((value[i] == '#' || value[i] == ';') && value[i - 1].isSpace()) {
|
||||
value = value.left(i).trimmed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Strip surrounding quotes, if any
|
||||
if (value.size() >= 2 &&
|
||||
((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\'')))) {
|
||||
value = value.mid(1, value.size() - 2).trimmed();
|
||||
}
|
||||
|
||||
int comma = value.lastIndexOf(',');
|
||||
|
||||
if (comma < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString family = value.left(comma).trimmed();
|
||||
|
||||
// Tolerate a partially-quoted family, e.g. "Inter", 10
|
||||
if (family.size() >= 2 &&
|
||||
((family.startsWith('"') && family.endsWith('"')) || (family.startsWith('\'') && family.endsWith('\'')))) {
|
||||
family = family.mid(1, family.size() - 2).trimmed();
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
int pointSize = value.mid(comma + 1).trimmed().toInt(&ok);
|
||||
|
||||
if (!ok || family.isEmpty() || pointSize <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.startsWith("AppFont::")) {
|
||||
key = key.mid(QStringLiteral("AppFont::").size());
|
||||
}
|
||||
|
||||
int roleInt = roleEnum.keyToValue(key.toUtf8().constData());
|
||||
|
||||
if (roleInt < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Canonical family + point size only: a fresh QFont carries default
|
||||
// (non-bold, non-italic) weight/style, so nothing caller-side leaks in.
|
||||
// No validation against installed fonts; Qt substitution applies.
|
||||
QFont font(family);
|
||||
font.setPointSize(pointSize);
|
||||
cfg.fonts[static_cast<AppFont::Role>(roleInt)] = font;
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
FontConfig FontConfig::fromThemeDir(const QString &themeDirPath)
|
||||
{
|
||||
if (themeDirPath.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return fromFile(QDir(themeDirPath).absoluteFilePath(fileName()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define COCKATRICE_THEME_CONFIG_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QMap>
|
||||
#include <QPalette>
|
||||
#include <QSharedPointer>
|
||||
|
|
@ -23,6 +24,23 @@ enum Role
|
|||
Q_ENUM_NS(Role)
|
||||
} // namespace AppColor
|
||||
|
||||
// Application-specific font roles, one configurable family + point size per
|
||||
// purpose. Stored in a shared fonts.toml under a [Fonts] section (one file per
|
||||
// theme, not split by light/dark). Only family and point size are stored;
|
||||
// weight/italic are stripped on load and stay caller-side concerns.
|
||||
namespace AppFont
|
||||
{
|
||||
Q_NAMESPACE
|
||||
enum Role
|
||||
{
|
||||
InterfaceDefault,
|
||||
ChatLog,
|
||||
CardText,
|
||||
DeckList,
|
||||
};
|
||||
Q_ENUM_NS(Role)
|
||||
} // namespace AppFont
|
||||
|
||||
struct ThemeConfig
|
||||
{
|
||||
QString colorScheme;
|
||||
|
|
@ -60,4 +78,33 @@ struct PaletteConfig
|
|||
QPalette apply(QPalette base) const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_THEME_CONFIG_H
|
||||
/** @brief Per-purpose font configuration, stored as family + point size only.
|
||||
*
|
||||
* Serialized to a shared fonts.toml file with a [Fonts] section, e.g.
|
||||
* @code
|
||||
* [Fonts]
|
||||
* InterfaceDefault = "Inter, 10"
|
||||
* @endcode
|
||||
*/
|
||||
struct FontConfig
|
||||
{
|
||||
QMap<AppFont::Role, QFont> fonts;
|
||||
|
||||
bool operator==(const FontConfig &rhs) const
|
||||
{
|
||||
return fonts == rhs.fonts;
|
||||
}
|
||||
bool operator!=(const FontConfig &rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
bool hasFonts() const;
|
||||
QString toToml() const;
|
||||
|
||||
static QString fileName();
|
||||
|
||||
static FontConfig fromFile(const QString &filePath);
|
||||
static FontConfig fromThemeDir(const QString &themeDirPath);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_THEME_CONFIG_H
|
||||
|
|
|
|||
|
|
@ -127,8 +127,10 @@ static QString usableDefaultStyle(const QString &style)
|
|||
ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
defaultStyleName = usableDefaultStyle(qApp->style()->objectName());
|
||||
// Capture the untouched application palette before any theme is applied.
|
||||
// Capture the untouched application palette and font before any theme is
|
||||
// applied.
|
||||
defaultPalette = qApp->palette();
|
||||
defaultFont = qApp->font();
|
||||
ensureThemeDirectoryExists();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
|
||||
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [this] {
|
||||
|
|
@ -440,6 +442,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 +530,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,
|
||||
|
|
@ -588,6 +648,26 @@ QColor ThemeManager::appColor(AppColor::Role role) const
|
|||
return accent;
|
||||
}
|
||||
|
||||
QFont ThemeManager::appFont(AppFont::Role role) const
|
||||
{
|
||||
const auto it = currentFonts.fonts.constFind(role);
|
||||
if (it != currentFonts.fonts.constEnd()) {
|
||||
return it.value();
|
||||
}
|
||||
|
||||
// No per-role scaling: every unset role resolves to the application font,
|
||||
// so with no fonts.toml present the result is identical to before.
|
||||
return qApp->font();
|
||||
}
|
||||
|
||||
QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const
|
||||
{
|
||||
// No per-role scaling and no preview: an unset role resolves to the font
|
||||
// captured at startup, which is what the theme falls back to once the
|
||||
// editor closes.
|
||||
return defaultFont;
|
||||
}
|
||||
|
||||
void ThemeManager::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getThemeName();
|
||||
|
|
@ -639,6 +719,28 @@ void ThemeManager::themeChangedSlot()
|
|||
|
||||
applyStyleAndPalette(themeName, themeCfg, palette, activeScheme);
|
||||
|
||||
// ── Load fonts: shared per theme, no light/dark split ───────────────────
|
||||
FontConfig fonts = loadFontConfig(dirPath);
|
||||
if (!fonts.hasFonts()) {
|
||||
// The shipped fonts.toml may live in the system theme directory rather
|
||||
// than the resolved (user) theme directory, so built-in themes still
|
||||
// get their curated fonts.
|
||||
const QString sysFontPath = QDir(systemThemesBasePath()).absoluteFilePath(themeName);
|
||||
if (sysFontPath != dirPath) {
|
||||
fonts = loadFontConfig(sysFontPath);
|
||||
}
|
||||
}
|
||||
currentFonts = fonts;
|
||||
const auto fontIt = currentFonts.fonts.constFind(AppFont::InterfaceDefault);
|
||||
if (fontIt != currentFonts.fonts.constEnd()) {
|
||||
qApp->setFont(fontIt.value());
|
||||
} 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();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QBrush>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
|
@ -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<QBrush, Role::MaxRole + 1> brushes;
|
||||
QStringMap availableThemes;
|
||||
QMap<AppColor::Role, QColor> 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 "<prefix>-<dark|light>"
|
||||
|
|
@ -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-<scheme>.toml and updates the
|
||||
* theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave
|
||||
* and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */
|
||||
|
|
@ -132,10 +144,19 @@ public:
|
|||
|
||||
void reloadCurrentTheme();
|
||||
void previewPalette(const PaletteConfig &cfg, const QString &scheme);
|
||||
void previewFonts(const FontConfig &cfg);
|
||||
|
||||
// Resolves an application color role: the theme's stored [AppColors] value
|
||||
// when present, otherwise a palette-accent-derived fallback.
|
||||
QColor appColor(AppColor::Role role) const;
|
||||
/** @brief Resolves an application font role: the theme's stored [Fonts]
|
||||
* value when present, otherwise the application font. */
|
||||
QFont appFont(AppFont::Role role) const;
|
||||
/** @brief Resolves an application font role against the startup font only,
|
||||
* ignoring any previewed config in currentFonts. Editors that show
|
||||
* "what this role resolves to" must use this, because appFont()
|
||||
* reflects a live preview while the editor is open. */
|
||||
QFont defaultAppFont(AppFont::Role role) const;
|
||||
|
||||
QBrush &getBgBrush(Role zone);
|
||||
QBrush getExtraBgBrush(Role zone, int zoneId = 0);
|
||||
|
|
@ -144,6 +165,7 @@ protected slots:
|
|||
signals:
|
||||
void themeChanged();
|
||||
void paletteChanged();
|
||||
void fontsChanged();
|
||||
};
|
||||
|
||||
extern ThemeManager *themeManager;
|
||||
|
|
|
|||
|
|
@ -312,6 +312,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
|
||||
loader_local_matching_test
|
||||
|
|
|
|||
195
tests/font_config_test.cpp
Normal file
195
tests/font_config_test.cpp
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#include "interface/theme_config.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QFont>
|
||||
#include <QIODevice>
|
||||
#include <QString>
|
||||
#include <QStringLiteral>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTextStream>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Writes content to a file inside tmp, creating parents as needed.
|
||||
QString writeTempFile(QTemporaryDir &tmp, const QString &relativePath, const QString &content)
|
||||
{
|
||||
const QString filePath = QDir(tmp.path()).absoluteFilePath(relativePath);
|
||||
EXPECT_TRUE(QDir().mkpath(QFileInfo(filePath).absolutePath()));
|
||||
QFile file(filePath);
|
||||
EXPECT_TRUE(file.open(QIODevice::WriteOnly | QIODevice::Text));
|
||||
QTextStream out(&file);
|
||||
out << content;
|
||||
file.close();
|
||||
return filePath;
|
||||
}
|
||||
|
||||
QFont makeFont(const QString &family, int pointSize)
|
||||
{
|
||||
QFont font(family);
|
||||
font.setPointSize(pointSize);
|
||||
return font;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(FontConfigTest, FileNameIsFontsToml)
|
||||
{
|
||||
EXPECT_EQ(FontConfig::fileName(), QStringLiteral("fonts.toml"));
|
||||
}
|
||||
|
||||
TEST(FontConfigTest, EmptyByDefault)
|
||||
{
|
||||
FontConfig cfg;
|
||||
EXPECT_FALSE(cfg.hasFonts());
|
||||
EXPECT_TRUE(cfg.toToml().isEmpty());
|
||||
EXPECT_EQ(cfg, FontConfig());
|
||||
EXPECT_EQ(cfg, FontConfig::fromFile("/nonexistent/path/fonts.toml"));
|
||||
EXPECT_EQ(cfg, FontConfig::fromThemeDir(QString()));
|
||||
}
|
||||
|
||||
TEST(FontConfigTest, RoundTripPreservesFamilyAndSize)
|
||||
{
|
||||
QTemporaryDir tmp;
|
||||
ASSERT_TRUE(tmp.isValid());
|
||||
|
||||
FontConfig cfg;
|
||||
cfg.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10);
|
||||
cfg.fonts[AppFont::ChatLog] = makeFont("JetBrains Mono", 9);
|
||||
cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 11);
|
||||
cfg.fonts[AppFont::DeckList] = makeFont("Roboto", 12);
|
||||
ASSERT_TRUE(cfg.hasFonts());
|
||||
|
||||
const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml());
|
||||
const FontConfig loaded = FontConfig::fromFile(filePath);
|
||||
|
||||
EXPECT_EQ(loaded, cfg);
|
||||
EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault].family(), QStringLiteral("Inter"));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault].pointSize(), 10);
|
||||
EXPECT_EQ(loaded.fonts[AppFont::ChatLog].family(), QStringLiteral("JetBrains Mono"));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::ChatLog].pointSize(), 9);
|
||||
EXPECT_EQ(loaded.fonts[AppFont::CardText].family(), QStringLiteral("Noto Serif"));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::CardText].pointSize(), 11);
|
||||
EXPECT_EQ(loaded.fonts[AppFont::DeckList].family(), QStringLiteral("Roboto"));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::DeckList].pointSize(), 12);
|
||||
}
|
||||
|
||||
TEST(FontConfigTest, RoundTripStripsWeightAndItalic)
|
||||
{
|
||||
QTemporaryDir tmp;
|
||||
ASSERT_TRUE(tmp.isValid());
|
||||
|
||||
QFont styled("Inter");
|
||||
styled.setPointSize(10);
|
||||
styled.setBold(true);
|
||||
styled.setItalic(true);
|
||||
|
||||
FontConfig cfg;
|
||||
cfg.fonts[AppFont::ChatLog] = styled;
|
||||
|
||||
const QString filePath = writeTempFile(tmp, "fonts.toml", cfg.toToml());
|
||||
const FontConfig loaded = FontConfig::fromFile(filePath);
|
||||
|
||||
ASSERT_TRUE(loaded.fonts.contains(AppFont::ChatLog));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::ChatLog].family(), QStringLiteral("Inter"));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::ChatLog].pointSize(), 10);
|
||||
EXPECT_FALSE(loaded.fonts[AppFont::ChatLog].bold());
|
||||
EXPECT_FALSE(loaded.fonts[AppFont::ChatLog].italic());
|
||||
EXPECT_EQ(loaded.fonts[AppFont::ChatLog], makeFont("Inter", 10));
|
||||
}
|
||||
|
||||
TEST(FontConfigTest, ToTomlFormat)
|
||||
{
|
||||
FontConfig cfg;
|
||||
cfg.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10);
|
||||
|
||||
const QString toml = cfg.toToml();
|
||||
EXPECT_TRUE(toml.contains(QStringLiteral("[Fonts]")));
|
||||
EXPECT_TRUE(toml.contains(QStringLiteral("InterfaceDefault")));
|
||||
EXPECT_TRUE(toml.contains(QStringLiteral("\"Inter, 10\"")));
|
||||
}
|
||||
|
||||
TEST(FontConfigTest, MalformedLinesAreSkipped)
|
||||
{
|
||||
QTemporaryDir tmp;
|
||||
ASSERT_TRUE(tmp.isValid());
|
||||
|
||||
const QString filePath = writeTempFile(tmp, "fonts.toml",
|
||||
"# a comment\n"
|
||||
"; another comment\n"
|
||||
"\n"
|
||||
"StrayBeforeSection = \"Inter, 10\"\n"
|
||||
"\n"
|
||||
"[Other]\n"
|
||||
"ChatLog = \"ShouldBeIgnored, 10\"\n"
|
||||
"\n"
|
||||
"[Fonts]\n"
|
||||
"InterfaceDefault = \"Inter, 10\"\n"
|
||||
"ChatLog = \"JetBrains Mono\", 9 # inline comment\n"
|
||||
"CardText = Noto Serif, 11\n"
|
||||
"AppFont::DeckList = \"Roboto, 12\"\n"
|
||||
"UnknownRole = \"Inter, 10\"\n"
|
||||
"QPalette::Window = \"Inter, 10\"\n"
|
||||
"NoComma = \"Inter\"\n"
|
||||
"EmptyFamily = \", 10\"\n"
|
||||
"ZeroSize = \"Inter, 0\"\n"
|
||||
"NegativeSize = \"Inter, -3\"\n"
|
||||
"NotANumber = \"Inter, big\"\n"
|
||||
"NotAnEntry\n");
|
||||
|
||||
const FontConfig loaded = FontConfig::fromFile(filePath);
|
||||
|
||||
ASSERT_TRUE(loaded.hasFonts());
|
||||
EXPECT_EQ(loaded.fonts.size(), 4);
|
||||
EXPECT_EQ(loaded.fonts[AppFont::InterfaceDefault], makeFont("Inter", 10));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::ChatLog], makeFont("JetBrains Mono", 9));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::CardText], makeFont("Noto Serif", 11));
|
||||
EXPECT_EQ(loaded.fonts[AppFont::DeckList], makeFont("Roboto", 12));
|
||||
}
|
||||
|
||||
TEST(FontConfigTest, FromThemeDirReadsFontsToml)
|
||||
{
|
||||
QTemporaryDir tmp;
|
||||
ASSERT_TRUE(tmp.isValid());
|
||||
|
||||
const QString themeDir = QDir(tmp.path()).absoluteFilePath("MyTheme");
|
||||
ASSERT_TRUE(QDir().mkpath(themeDir));
|
||||
|
||||
FontConfig cfg;
|
||||
cfg.fonts[AppFont::CardText] = makeFont("Noto Serif", 11);
|
||||
writeTempFile(tmp, "MyTheme/fonts.toml", cfg.toToml());
|
||||
|
||||
const FontConfig loaded = FontConfig::fromThemeDir(themeDir);
|
||||
EXPECT_EQ(loaded, cfg);
|
||||
|
||||
// A theme directory without fonts.toml loads as empty.
|
||||
const QString bareDir = QDir(tmp.path()).absoluteFilePath("BareTheme");
|
||||
ASSERT_TRUE(QDir().mkpath(bareDir));
|
||||
EXPECT_FALSE(FontConfig::fromThemeDir(bareDir).hasFonts());
|
||||
}
|
||||
|
||||
TEST(FontConfigTest, InequalityComparesRoles)
|
||||
{
|
||||
FontConfig left;
|
||||
left.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 10);
|
||||
|
||||
FontConfig right = left;
|
||||
EXPECT_EQ(left, right);
|
||||
EXPECT_FALSE(left != right);
|
||||
|
||||
right.fonts[AppFont::InterfaceDefault] = makeFont("Inter", 11);
|
||||
EXPECT_NE(left, right);
|
||||
|
||||
right = left;
|
||||
right.fonts[AppFont::ChatLog] = makeFont("Inter", 10);
|
||||
EXPECT_NE(left, right);
|
||||
}
|
||||
|
||||
// Every test brings its own main(): GTest is not guaranteed to be found on the build machines, and
|
||||
// the fallback then links the bare gtest library without gtest_main.
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue