mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-10 03:53:26 -08:00
[Fonts] Retire max font size setting into theme CardText min/max range (#7544)
* [Fonts] Retire max font size setting into theme CardText min/max range The per-card text size lives in the theme now, so a theme can pick the size it was designed for instead of every user overriding it globally. - CardText stores family + max point size; CardTextMinSize stores the overlay shrink-to-fit floor (omitted from fonts.toml when 1). - ThemeManager::cardTextMaxSize()/cardTextMinSize() resolve the range with the retired defaults (12 max, 1 min), and cardTextMaxSize() falls back to the CardText role size that #7541 made live. - The overlay widget starts at the explicit size when one is set (deck preview), else the theme max, and shrinks down to the theme floor. - The appearance page's "Maximum font size" spin is gone; the range is edited in the theme's font grid, whose min/max spins now keep the saved range consistent with each other. - Migration: appearance/maxFontSize is seeded into every user theme folder and into the active theme's folder (created when the theme is a shipped one that only exists in the system directory). The retired game/maxfontsize -> appearance/maxFontSize mapping stays, so users still on global.ini keep their value through the chain. * [Fonts] Resolve CardText sizes against the shipped theme Seeding themes/<name>/fonts.toml made getAvailableThemes() resolve the user dir, which shadowed the shipped theme.cfg and stylesheet: styleName came back empty and QSS was cleared. Fall back to the shipped theme dir for theme.cfg and QSS when the user dir exists, and resolve CardText sizes through FontConfig's 6..72 range so a migrated 80..100 renders at the size the editor shows. Insert CardTextMaxSize inside [Fonts] when another section follows. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
16455c9fd5
commit
3d24597134
21 changed files with 1058 additions and 74 deletions
|
|
@ -163,6 +163,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);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@
|
|||
#include <QVariant>
|
||||
#include <QtGlobal>
|
||||
#include <algorithm>
|
||||
#include <libcockatrice/settings/appearance_settings.h>
|
||||
#include <libcockatrice/settings/cards_display_settings.h>
|
||||
#include <libcockatrice/settings/debug_settings.h>
|
||||
#include <qnamespace.h>
|
||||
|
|
@ -187,8 +186,8 @@ bool AbstractCardItem::shouldPaintNameOverPicture() 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());
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@
|
|||
#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 = {
|
||||
|
|
@ -46,6 +44,7 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
|
|||
grid->setColumnStretch(0, 0);
|
||||
grid->setColumnStretch(1, 1);
|
||||
grid->setColumnStretch(2, 0);
|
||||
grid->setColumnStretch(3, 0);
|
||||
|
||||
QMetaEnum fontEnum = QMetaEnum::fromType<AppFont::Role>();
|
||||
for (int i = 0; i < fontEnum.keyCount(); ++i) {
|
||||
|
|
@ -58,14 +57,41 @@ FontGridWidget::FontGridWidget(QWidget *parent) : QWidget(parent)
|
|||
grid->addWidget(familyCombo, i, 1);
|
||||
connect(familyCombo, &QFontComboBox::currentFontChanged, this, [this] { emit fontsChanged(); });
|
||||
|
||||
if (role == AppFont::CardText) {
|
||||
auto *minSpin = new QSpinBox(this);
|
||||
minSpin->setRange(FontConfig::CARD_TEXT_MIN_SIZE, FontConfig::MAX_FONT_SIZE);
|
||||
grid->addWidget(minSpin, i, 2);
|
||||
connect(minSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
|
||||
|
||||
auto *maxSpin = new QSpinBox(this);
|
||||
maxSpin->setRange(FontConfig::MIN_FONT_SIZE, FontConfig::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, but never below the max control's
|
||||
// own floor: the renderer resolves the max into the same range, so a
|
||||
// smaller value here would be the same mismatch in reverse.
|
||||
connect(minSpin, &QSpinBox::valueChanged, this,
|
||||
[maxSpin](int value) { maxSpin->setMinimum(qMax(value, FontConfig::MIN_FONT_SIZE)); });
|
||||
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);
|
||||
sizeSpin->setRange(FontConfig::MIN_FONT_SIZE, FontConfig::MAX_FONT_SIZE);
|
||||
grid->addWidget(sizeSpin, i, 2);
|
||||
connect(sizeSpin, &QSpinBox::valueChanged, this, [this] { emit fontsChanged(); });
|
||||
|
||||
label->setBuddy(familyCombo);
|
||||
|
||||
fontRows[role] = {label, familyCombo, sizeSpin};
|
||||
fontRows[role] = {label, familyCombo, sizeSpin, nullptr};
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
|
|
@ -89,6 +115,25 @@ void FontGridWidget::loadFonts(const FontConfig &cfg)
|
|||
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(FontConfig::CARD_TEXT_MIN_SIZE, FontConfig::MAX_FONT_SIZE);
|
||||
row.sizeSpin->setRange(FontConfig::MIN_FONT_SIZE, FontConfig::MAX_FONT_SIZE);
|
||||
|
||||
// Resolved by FontConfig, which the renderer reads through as well:
|
||||
// a stored size the editor cannot represent (for example a migrated
|
||||
// appearance/maxFontSize above 72) shows at the bound the board
|
||||
// draws, so a save never rewrites a size that was in use.
|
||||
// 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(cfg.resolvedCardTextMaxSize());
|
||||
row.minSpin->setValue(cfg.resolvedCardTextMinSize());
|
||||
continue;
|
||||
}
|
||||
|
||||
int size = font.pointSize();
|
||||
if (size <= 0) {
|
||||
size = qApp->font().pointSize();
|
||||
|
|
@ -96,7 +141,7 @@ void FontGridWidget::loadFonts(const FontConfig &cfg)
|
|||
if (size <= 0) {
|
||||
size = FALLBACK_FONT_SIZE;
|
||||
}
|
||||
row.sizeSpin->setValue(qBound(MIN_FONT_SIZE, size, MAX_FONT_SIZE));
|
||||
row.sizeSpin->setValue(qBound(FontConfig::MIN_FONT_SIZE, size, FontConfig::MAX_FONT_SIZE));
|
||||
}
|
||||
blockSignals(false);
|
||||
}
|
||||
|
|
@ -114,6 +159,11 @@ FontConfig FontGridWidget::currentFontConfig() const
|
|||
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;
|
||||
}
|
||||
|
|
@ -129,6 +179,15 @@ void FontGridWidget::retranslateUi()
|
|||
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"));
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ private:
|
|||
QLabel *label = nullptr;
|
||||
QFontComboBox *familyCombo = nullptr;
|
||||
QSpinBox *sizeSpin = nullptr;
|
||||
QSpinBox *minSpin = nullptr; // CardText only: shrink-to-fit floor
|
||||
};
|
||||
|
||||
QMap<AppFont::Role, FontRow> fontRows;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <QPair>
|
||||
#include <QStringLiteral>
|
||||
#include <QTextStream>
|
||||
#include <algorithm>
|
||||
#include <qnamespace.h>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -309,7 +310,28 @@ QPalette PaletteConfig::apply(QPalette base) const
|
|||
|
||||
bool FontConfig::hasFonts() const
|
||||
{
|
||||
return !fonts.isEmpty();
|
||||
return !fonts.isEmpty() || cardTextMinSize != CARD_TEXT_MIN_SIZE || cardTextMaxSize > 0;
|
||||
}
|
||||
|
||||
int FontConfig::resolvedCardTextMinSize() const
|
||||
{
|
||||
const int size = cardTextMinSize > 0 ? cardTextMinSize : CARD_TEXT_MIN_SIZE;
|
||||
return std::clamp(size, CARD_TEXT_MIN_SIZE, MAX_FONT_SIZE);
|
||||
}
|
||||
|
||||
int FontConfig::resolvedCardTextMaxSize() const
|
||||
{
|
||||
int size = cardTextMaxSize;
|
||||
if (size <= 0) {
|
||||
const auto it = fonts.constFind(AppFont::CardText);
|
||||
size = it != fonts.constEnd() ? it.value().pointSize() : 0;
|
||||
}
|
||||
if (size <= 0) {
|
||||
size = DEFAULT_CARD_TEXT_SIZE;
|
||||
}
|
||||
// Mirror the editor's resolution: clamp the max into its control range, then
|
||||
// raise it to the (already clamped) min so the pair stays consistent.
|
||||
return std::max(resolvedCardTextMinSize(), std::clamp(size, MIN_FONT_SIZE, MAX_FONT_SIZE));
|
||||
}
|
||||
|
||||
QString FontConfig::toToml() const
|
||||
|
|
@ -335,6 +357,14 @@ QString FontConfig::toToml() const
|
|||
entries += QString("%1 = \"%2, %3\"\n").arg(QString(roleName), -20).arg(family).arg(pointSize);
|
||||
}
|
||||
|
||||
if (cardTextMinSize != CARD_TEXT_MIN_SIZE) {
|
||||
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 {};
|
||||
}
|
||||
|
|
@ -402,6 +432,31 @@ FontConfig FontConfig::fromFile(const QString &filePath)
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -128,15 +128,41 @@ struct PaletteConfig
|
|||
* @code
|
||||
* [Fonts]
|
||||
* InterfaceDefault = "Inter, 10"
|
||||
* CardText = "Noto Serif, 12"
|
||||
* CardTextMinSize = 1
|
||||
* @endcode
|
||||
* CardText stores family + MAX size; cardTextMinSize stores the overlay
|
||||
* shrink-to-fit floor (omitted from TOML when 1).
|
||||
*/
|
||||
struct FontConfig
|
||||
{
|
||||
/// Point-size range the theme font editor can represent. CardText is
|
||||
/// resolved through the same bounds where it is rendered, so the board never
|
||||
/// draws a size the editor cannot show and would clamp away on save.
|
||||
static constexpr int MIN_FONT_SIZE = 6;
|
||||
static constexpr int MAX_FONT_SIZE = 72;
|
||||
/// CardText shrink-to-fit floor, editable below the general font floor.
|
||||
static constexpr int CARD_TEXT_MIN_SIZE = 1;
|
||||
/// CardText size when neither CardTextMaxSize nor a CardText family is set.
|
||||
static constexpr int DEFAULT_CARD_TEXT_SIZE = 12;
|
||||
|
||||
QMap<AppFont::Role, QFont> fonts;
|
||||
int cardTextMinSize = CARD_TEXT_MIN_SIZE;
|
||||
/// 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;
|
||||
|
||||
/// CardText min/max resolved to what the font editor shows and a save would
|
||||
/// write: the stored values (max falling back to the CardText point size,
|
||||
/// then the default) clamped into the representable range. A seeded legacy
|
||||
/// size above the editor's maximum therefore renders at that maximum instead
|
||||
/// of at a size the editor would silently rewrite.
|
||||
int resolvedCardTextMinSize() const;
|
||||
int resolvedCardTextMaxSize() const;
|
||||
|
||||
bool operator==(const FontConfig &rhs) const
|
||||
{
|
||||
return fonts == rhs.fonts;
|
||||
return fonts == rhs.fonts && cardTextMinSize == rhs.cardTextMinSize && cardTextMaxSize == rhs.cardTextMaxSize;
|
||||
}
|
||||
bool operator!=(const FontConfig &rhs) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ static const QColor PLAYERZONE_BG_DEFAULT = QColor(200, 200, 200);
|
|||
static const QColor STACKZONE_BG_DEFAULT = QColor(113, 43, 43);
|
||||
static const QStringList DEFAULT_RESOURCE_PATHS = {":/resources"};
|
||||
|
||||
// Defined below; forward-declared because isDarkMode() resolves the shipped
|
||||
// theme directory needed for the theme.cfg fallback.
|
||||
static QString systemThemesBasePath();
|
||||
|
||||
struct PaletteColorInfo
|
||||
{
|
||||
QPalette::ColorGroup group;
|
||||
|
|
@ -236,7 +240,15 @@ void ThemeManager::ensureThemeDirectoryExists()
|
|||
|
||||
bool ThemeManager::isDarkMode(const QString &themeDirPath) const
|
||||
{
|
||||
ThemeConfig themeConfig = ThemeConfig::fromThemeDir(themeDirPath);
|
||||
// The theme directory is always named after the theme, so the shipped
|
||||
// fallback for a user directory that carries no theme.cfg is the same-named
|
||||
// directory under the system themes base path — mirroring the font and
|
||||
// palette fallbacks. A resolved directory that does not exist yet is a stock
|
||||
// theme (getAvailableThemes() maps System/Fusion to a user path that
|
||||
// writableThemeDir() only creates on save), so it keeps following the OS
|
||||
// instead of inheriting a shipped ColorScheme such as Dark.
|
||||
const QString systemThemeDir = QDir(systemThemesBasePath()).absoluteFilePath(QFileInfo(themeDirPath).fileName());
|
||||
const ThemeConfig themeConfig = loadColorSchemeConfig(themeDirPath, systemThemeDir);
|
||||
if (themeConfig.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) {
|
||||
return true;
|
||||
} else if (themeConfig.colorScheme.compare("Light", Qt::CaseInsensitive) == 0) {
|
||||
|
|
@ -402,6 +414,60 @@ bool ThemeManager::saveGlobalConfig(const QString &themeDirPath, const ThemeConf
|
|||
return cfg.save(themeDirPath);
|
||||
}
|
||||
|
||||
ThemeConfig ThemeManager::loadDefaultThemeConfig(const QString &themeDirPath, const QString &systemThemeDirPath)
|
||||
{
|
||||
if (themeDirPath.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// The shipped theme.cfg may live in the system theme directory rather than
|
||||
// the resolved (user) directory. A user directory that only holds a seeded
|
||||
// fonts.toml (or a saved palette) must still inherit the shipped style and
|
||||
// colour scheme instead of shadowing them with an empty config.
|
||||
if (QFileInfo::exists(QDir(themeDirPath).absoluteFilePath("theme.cfg"))) {
|
||||
return ThemeConfig::fromThemeDir(themeDirPath);
|
||||
}
|
||||
return ThemeConfig::fromThemeDir(systemThemeDirPath);
|
||||
}
|
||||
|
||||
ThemeConfig ThemeManager::loadColorSchemeConfig(const QString &themeDirPath, const QString &systemThemeDirPath)
|
||||
{
|
||||
// Only an existing user directory is layered on the shipped theme: it can
|
||||
// hold seeded files (fonts.toml, a saved palette) without a theme.cfg of its
|
||||
// own, so it inherits the shipped style and colour scheme. A directory that
|
||||
// does not exist yet means the user has never customised this theme, and
|
||||
// inheriting a shipped Dark scheme there would override the OS preference on
|
||||
// a fresh profile.
|
||||
if (themeDirPath.isEmpty() || !QFileInfo(themeDirPath).isDir()) {
|
||||
return {};
|
||||
}
|
||||
return loadDefaultThemeConfig(themeDirPath, systemThemeDirPath);
|
||||
}
|
||||
|
||||
QString ThemeManager::themeStyleSheetPath(const QString &themeDirPath, const QString &systemThemeDirPath, bool dark)
|
||||
{
|
||||
const QString schemeCss = QFileInfo(QStringLiteral(STYLE_CSS_NAME)).completeBaseName() + QLatin1Char('-') +
|
||||
(dark ? QStringLiteral("dark") : QStringLiteral("light")) + QStringLiteral(".css");
|
||||
|
||||
// Scheme-qualified stylesheet first, plain style.css as the super fallback,
|
||||
// both inside the resolved (user) directory before the shipped one. A user
|
||||
// directory holding only fonts.toml therefore keeps the shipped styling.
|
||||
const QStringList candidateDirs = {themeDirPath, systemThemeDirPath};
|
||||
const QStringList candidateNames = {schemeCss, QString(STYLE_CSS_NAME)};
|
||||
for (const QString &dirPath : candidateDirs) {
|
||||
if (dirPath.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (const QString &name : candidateNames) {
|
||||
const QString path = QDir(dirPath).absoluteFilePath(name);
|
||||
if (QFileInfo::exists(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
PaletteConfig ThemeManager::loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme)
|
||||
{
|
||||
if (themeDirPath.isEmpty()) {
|
||||
|
|
@ -528,7 +594,10 @@ void ThemeManager::previewPalette(const PaletteConfig &cfg, const QString &schem
|
|||
{
|
||||
const QString themeName = SettingsCache::instance().getThemeName();
|
||||
const QString dirPath = getAvailableThemes().value(themeName);
|
||||
const ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
// Same shipped fallback as themeChangedSlot(): a user directory that only
|
||||
// holds seeded files must not preview the theme with its style reset.
|
||||
const QString sysPath = QDir(systemThemesBasePath()).absoluteFilePath(themeName);
|
||||
const ThemeConfig themeCfg = loadDefaultThemeConfig(dirPath, sysPath);
|
||||
applyStyleAndPalette(themeName, themeCfg, cfg, scheme);
|
||||
}
|
||||
|
||||
|
|
@ -732,6 +801,7 @@ QFont ThemeManager::cardTextFont() const
|
|||
}
|
||||
QFont serifFont(QStringLiteral("Serif"));
|
||||
serifFont.setStyleHint(QFont::Serif);
|
||||
serifFont.setPointSize(12);
|
||||
return serifFont;
|
||||
}
|
||||
|
||||
|
|
@ -753,37 +823,44 @@ QFont ThemeManager::defaultAppFont(AppFont::Role /*role*/) const
|
|||
return defaultFont;
|
||||
}
|
||||
|
||||
int ThemeManager::cardTextMaxSize() const
|
||||
{
|
||||
// Resolved through FontConfig so the board and the font editor share one
|
||||
// representable range: a stored size the editor cannot show (e.g. a migrated
|
||||
// appearance/maxFontSize of 100) renders at the editor's maximum instead of
|
||||
// at a size the next save would silently rewrite.
|
||||
return currentFonts.resolvedCardTextMaxSize();
|
||||
}
|
||||
|
||||
int ThemeManager::cardTextMinSize() const
|
||||
{
|
||||
return currentFonts.resolvedCardTextMinSize();
|
||||
}
|
||||
|
||||
void ThemeManager::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getThemeName();
|
||||
QString dirPath = getAvailableThemes().value(themeName);
|
||||
currentThemePath = dirPath;
|
||||
QDir dir(dirPath);
|
||||
const QString sysPath = QDir(systemThemesBasePath()).absoluteFilePath(themeName);
|
||||
|
||||
// CSS — prefer the scheme-qualified stylesheet (style-dark.css /
|
||||
// style-light.css) when present, else the plain style.css as fallback.
|
||||
if (!dirPath.isEmpty()) {
|
||||
const QString scheme = isDarkMode(dirPath) ? QStringLiteral("dark") : QStringLiteral("light");
|
||||
const QString schemeCss = QFileInfo(QStringLiteral(STYLE_CSS_NAME)).completeBaseName() + QLatin1Char('-') +
|
||||
scheme + QStringLiteral(".css");
|
||||
if (dir.exists(schemeCss)) {
|
||||
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(schemeCss));
|
||||
} else if (dir.exists(STYLE_CSS_NAME)) {
|
||||
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
|
||||
} else {
|
||||
qApp->setStyleSheet("");
|
||||
}
|
||||
} else {
|
||||
qApp->setStyleSheet("");
|
||||
}
|
||||
// A user theme directory that only carries seeded files falls back to the
|
||||
// shipped stylesheet of the same theme.
|
||||
const bool darkMode = isDarkMode(dirPath);
|
||||
const QString styleSheetPath = themeStyleSheetPath(dirPath, sysPath, darkMode);
|
||||
qApp->setStyleSheet(styleSheetPath.isEmpty() ? QString() : "file:///" + styleSheetPath);
|
||||
|
||||
// load theme.cfg for style + scheme preference
|
||||
ThemeConfig themeCfg = ThemeConfig::fromThemeDir(dirPath);
|
||||
// load theme.cfg for style + scheme preference, falling back to the shipped
|
||||
// theme directory when the user copy does not carry one
|
||||
ThemeConfig themeCfg = loadDefaultThemeConfig(dirPath, sysPath);
|
||||
|
||||
// Resolve active scheme:
|
||||
// theme.cfg says Dark/Light → use that
|
||||
// theme.cfg says System or is absent → follow the OS
|
||||
QString activeScheme = isDarkMode(dirPath) ? "Dark" : "Light";
|
||||
QString activeScheme = darkMode ? "Dark" : "Light";
|
||||
|
||||
// ── Load palette: custom first, then theme default ────────────────────
|
||||
PaletteConfig palette = PaletteConfig::fromScheme(dirPath, activeScheme);
|
||||
|
|
@ -826,7 +903,6 @@ void ThemeManager::themeChangedSlot()
|
|||
// include the system theme dir as a fallback so shipped assets like
|
||||
// zones/*.png and style.css still resolve for themes that ship only
|
||||
// those files (e.g. Leather, Plasma, Fabric, VelvetMarble).
|
||||
const QString sysPath = QDir(systemThemesBasePath()).absoluteFilePath(themeName);
|
||||
if (sysPath != dirPath && QDir(sysPath).exists()) {
|
||||
resources << sysPath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ public:
|
|||
static bool isDirReallyWritable(const QString &dirPath);
|
||||
// Explicit color scheme of the theme: theme.cfg's ColorScheme setting
|
||||
// (Dark/Light), falling back to the OS color scheme when it is "System".
|
||||
// A resolved directory that does not exist yet is an uncustomised stock
|
||||
// theme, so it follows the OS rather than the shipped scheme.
|
||||
bool isDarkMode(const QString &themeDirPath) const;
|
||||
// The resolved scheme of the currently active theme.
|
||||
bool isDarkModeActive() const
|
||||
|
|
@ -107,6 +109,20 @@ public:
|
|||
// Load the global theme settings (style + color scheme preference)
|
||||
static ThemeConfig loadGlobalConfig(const QString &themeDirPath);
|
||||
static bool saveGlobalConfig(const QString &themeDirPath, const ThemeConfig &cfg);
|
||||
// Load a theme's theme.cfg, falling back to the shipped (system) theme
|
||||
// directory when the resolved user directory does not carry one. A user
|
||||
// directory seeded with only fonts.toml would otherwise shadow the shipped
|
||||
// theme and lose its style and colour scheme.
|
||||
static ThemeConfig loadDefaultThemeConfig(const QString &themeDirPath, const QString &systemThemeDirPath);
|
||||
// Load the theme.cfg that decides the active colour scheme. Unlike
|
||||
// loadDefaultThemeConfig(), a resolved directory that does not exist yet is
|
||||
// a stock theme with no user override: it returns an empty config so the
|
||||
// caller follows the OS scheme instead of inheriting the shipped one.
|
||||
static ThemeConfig loadColorSchemeConfig(const QString &themeDirPath, const QString &systemThemeDirPath);
|
||||
// Resolve the theme stylesheet: the scheme-qualified style-<dark|light>.css
|
||||
// when shipped, else style.css, preferring the resolved (user) directory
|
||||
// over the shipped one. Empty when neither directory carries a stylesheet.
|
||||
static QString themeStyleSheetPath(const QString &themeDirPath, const QString &systemThemeDirPath, bool dark);
|
||||
|
||||
// Load/save per-scheme palette colors
|
||||
static PaletteConfig loadPaletteConfig(const QString &themeDirPath, const QString &colorScheme);
|
||||
|
|
@ -170,6 +186,12 @@ public:
|
|||
* "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 resolved the way the font editor shows it:
|
||||
* stored CardTextMaxSize, else the CardText point size, else 12,
|
||||
* clamped to the editor's representable range. */
|
||||
int cardTextMaxSize() const;
|
||||
/** @brief CardText shrink-to-fit floor resolved the same way. */
|
||||
int cardTextMinSize() const;
|
||||
|
||||
QBrush &getBgBrush(Role zone);
|
||||
QBrush getExtraBgBrush(Role zone, int zoneId = 0);
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ void CardInfoPictureWithTextOverlayWidget::setOutlineColor(const QColor &color)
|
|||
void CardInfoPictureWithTextOverlayWidget::setFontSize(const int size)
|
||||
{
|
||||
fontSize = size > 0 ? size : 1;
|
||||
hasExplicitFontSize = true;
|
||||
update();
|
||||
}
|
||||
|
||||
|
|
@ -179,10 +180,12 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
|
|||
}
|
||||
|
||||
// Calculate the optimal font size. The family comes from the CardText
|
||||
// theme role; the fontSize member stays the starting size and the
|
||||
// shrink-to-fit logic below is unchanged.
|
||||
// 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);
|
||||
int optimalFontSize = fontSize; // Start with the user-defined font size
|
||||
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();
|
||||
|
||||
|
|
@ -200,7 +203,7 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event)
|
|||
}
|
||||
|
||||
--optimalFontSize;
|
||||
} while (optimalFontSize > 1);
|
||||
} while (optimalFontSize > themeMin);
|
||||
|
||||
// Apply the calculated font size
|
||||
painter.setFont(font);
|
||||
|
|
@ -227,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);
|
||||
|
|
@ -297,7 +300,9 @@ QSize CardInfoPictureWithTextOverlayWidget::minimumSizeHint() const
|
|||
|
||||
// Get the font metrics for the overlay text
|
||||
QFont font = themeManager->appFont(AppFont::CardText);
|
||||
font.setPointSize(fontSize);
|
||||
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
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ private:
|
|||
QColor textColor;
|
||||
QColor outlineColor;
|
||||
int fontSize;
|
||||
bool hasExplicitFontSize = false;
|
||||
Qt::Alignment textAlignment;
|
||||
bool highlighted;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -284,20 +284,11 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
|||
connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings.cardsDisplay(),
|
||||
&CardsDisplaySettings::setRoundCardCorners);
|
||||
|
||||
connect(&maxFontSizeForCardsEdit, qOverload<int>(&QSpinBox::valueChanged), &settings.appearance(),
|
||||
&AppearanceSettings::setMaxFontSize);
|
||||
maxFontSizeForCardsEdit.setValue(settings.appearance().getMaxFontSize());
|
||||
maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit);
|
||||
maxFontSizeForCardsEdit.setMinimum(9);
|
||||
maxFontSizeForCardsEdit.setMaximum(100);
|
||||
|
||||
auto *cardsGrid = new QGridLayout;
|
||||
cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2);
|
||||
cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2);
|
||||
cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2);
|
||||
cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2);
|
||||
cardsGrid->addWidget(&maxFontSizeForCardsLabel, 4, 0, 1, 1);
|
||||
cardsGrid->addWidget(&maxFontSizeForCardsEdit, 4, 1, 1, 1);
|
||||
|
||||
cardsGroupBox = new QGroupBox;
|
||||
cardsGroupBox->setLayout(cardsGrid);
|
||||
|
|
@ -658,7 +649,6 @@ void AppearanceSettingsPage::retranslateUi()
|
|||
autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout"));
|
||||
cardScalingCheckBox.setText(tr("Scale cards on mouse over"));
|
||||
roundCardCornersCheckBox.setText(tr("Use rounded card corners"));
|
||||
maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:"));
|
||||
|
||||
cardLayoutGroupBox->setTitle(tr("Card layout"));
|
||||
verticalCardOverlapPercentLabel.setText(
|
||||
|
|
|
|||
|
|
@ -71,8 +71,6 @@ private:
|
|||
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
|
||||
QCheckBox cardScalingCheckBox;
|
||||
QCheckBox roundCardCornersCheckBox;
|
||||
QLabel maxFontSizeForCardsLabel;
|
||||
QSpinBox maxFontSizeForCardsEdit;
|
||||
|
||||
QLabel verticalCardOverlapPercentLabel;
|
||||
QSpinBox verticalCardOverlapPercentBox;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue