[Icons] Migrate UI glyphs to palette-token SVGs (#7595)

* [Icons] Migrate UI glyphs to palette-token SVGs

Runtime inversion is not a theming system: it rotates hue, breaks duotone art
and mishandles alpha, and a QPixmap snapshot cannot follow a palette change.
Redraw the top-level UI glyph set on a 24x24 grid with `currentColor` as the
paint token, drop Inkscape/Sodipodi cruft, and delete the icons whose metaphor
was duplicated (cogwheel/gear -> settings, scale_balanced -> scales,
update.png -> reload).

Load the glyphs through `loadThemedIcon` so the engine resolves the token from
the palette on every request, and let `themePixmap` use the same resolution for
one-shot pixmaps. `loadColorAdjustedPixmap` is gone; every caller now keeps a
QIcon, which `SettingsButtonWidget::setButtonIcon` accepts instead of a QPixmap.

Content art (mana, counters, countries) keeps its authored colors.

Add a render test covering aspect ratio, palette flips, icon modes and
high-DPI pixmaps.

* [Tests] Keep the icon render test QApplication alive until exit

Creating it as a function-local static tears it down before Qt's library statics
at exit, and libQt6Network then runs QObject destructors against freed memory —
a SEGV the sanitizer leg reports after the tests have already passed. Leak the
application instead, the usual pattern for a test binary.

* [Icons] Render lock and dropdown glyphs through the icon engine

Both generators rendered the sentinel SVG to a fixed height x height pixmap
at DPR 1. The pre-migration path rendered larger and let QIcon scale it, so
on a 2x display the games-list lock (13px) and the banner dropdown arrow
(24px) came back crisp; the new path hands the widget a DPR-1 bitmap that
gets upscaled and blurred.

- The games list asks the themed engine for `icons/lock` directly, so the
  item view paints it at the device pixel ratio and the lock also follows
  HighlightedText on a selected row.
- The dropdown goes through `loadThemedIcon(...).pixmap(size, dpr)`, which
  keeps the glyph's aspect ratio and renders at the requested ratio.
- LockPixmapGenerator has no consumer left, so it and its cache are gone.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-11-04 18:39:41 +01:00 • committed by GitHub
parent 020d9e8dcb
commit 627c7a83aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
97 changed files with 543 additions and 9711 deletions

View file

@ -1,6 +1,7 @@
#include "pixel_map_generator.h"
#include "libcockatrice/network/server/remote/user_level.h"
#include "theme_icon.h"
#include "theme_manager.h"
#include <QApplication>
@ -15,7 +16,6 @@
#include <QDomNodeList>
#include <QFile>
#include <QIODevice>
#include <QImage>
#include <QImageReader>
#include <QLatin1Char>
#include <QLatin1String>
@ -436,33 +436,19 @@ QIcon UserLevelPixmapGenerator::generateIconWithColorOverride(int height,
QMap<QString, QIcon> UserLevelPixmapGenerator::iconCache;
QPixmap LockPixmapGenerator::generatePixmap(int height)
{
int key = height;
if (pmCache.contains(key)) {
return pmCache.value(key);
}
QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/lock")),
QSize(height, height), true);
pmCache.insert(key, pixmap);
return pixmap;
}
QMap<int, QPixmap> LockPixmapGenerator::pmCache;
QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
{
QString key = QString::number(expanded) + ":" + QString::number(height);
const QString key = QString::number(expanded) + ":" + QString::number(height);
if (pmCache.contains(key)) {
return pmCache.value(key);
}
QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
QPixmap pixmap = tryLoadImage(QStringLiteral("theme:") + themeManager->assetPath(QStringLiteral("icons/") + name),
QSize(height, height), true);
// Render through the themed icon engine: it keeps the glyph's aspect ratio,
// follows the palette, and renders at the device pixel ratio instead of
// handing a DPR-1 bitmap to a label that would upscale and blur it on a 2x
// display.
const QString prefix = QStringLiteral("icons/") + (expanded ? "dropdown_expanded" : "dropdown_collapsed");
const QPixmap pixmap = loadThemedIcon(prefix).pixmap(QSize(height, height), qApp->devicePixelRatio());
pmCache.insert(key, pixmap);
return pixmap;
}
@ -520,30 +506,11 @@ QPixmap ManaSymbolPixmapGenerator::generatePixmap(const QString &symbol, const Q
QHash<QString, QPixmap> ManaSymbolPixmapGenerator::masterCache;
QHash<QString, QPixmap> ManaSymbolPixmapGenerator::scaledCache;
QPixmap loadColorAdjustedPixmap(const QString &name)
{
// Prefer an authored scheme-qualified variant when one exists for this asset.
const QString variant = themeManager->schemeVariantPath(QStringView(name).mid(QStringLiteral("theme:").size()));
if (!variant.isEmpty()) {
return QPixmap(QStringLiteral("theme:") + variant);
}
// Legacy fallback: runtime-invert for dark mode when no authored variant.
if (qApp->palette().windowText().color().lightness() > 200) {
QImage img(name);
img.invertPixels();
QPixmap result;
result.convertFromImage(img);
return result;
} else {
return QPixmap(name);
}
}
QPixmap themePixmap(QStringView prefix)
{
const QString resolved = themeManager->assetPath(prefix);
return QPixmap(QStringLiteral("theme:") + resolved);
const QPixmap sentinel = loadSentinelSvgPixmap(resolved);
return sentinel.isNull() ? QPixmap(QStringLiteral("theme:") + resolved) : sentinel;
}
void clearPixmapGeneratorCaches()
@ -553,7 +520,6 @@ void clearPixmapGeneratorCaches()
PingPixmapGenerator::clear();
CountryPixmapGenerator::clear();
UserLevelPixmapGenerator::clear();
LockPixmapGenerator::clear();
DropdownIconPixmapGenerator::clear();
ManaSymbolPixmapGenerator::clear();
}