[PrintingSelector] Load the pin badge through the themed icon engine (#7598)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions

The badge rendered `theme:icons/pin` through the pixmap generator, which
resolved `currentColor` once and cached the result, so the glyph stayed at its
first palette and was rasterized at DPR 1.

- Render the glyph through `loadThemedIcon`, at the label's device pixel ratio,
  and re-render it on palette and theme changes.
- Compose it onto a rounded backdrop filled with `Window` and outlined with
  `WindowText`. The badge floats on card art rather than on a palette surface,
  so a bare outline vanishes into art of the same lightness; the backdrop keeps
  it readable on every printing, and both roles still follow the palette.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-11-04 18:39:43 +01:00 • committed by GitHub
parent ada0cccfbf
commit df74917cb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -2,6 +2,8 @@
#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/cache_settings.h"
#include "../../card_picture_loader/card_picture_loader.h" #include "../../card_picture_loader/card_picture_loader.h"
#include "../../theme_icon.h"
#include "../../theme_manager.h"
#include "../cards/card_info_picture_widget.h" #include "../cards/card_info_picture_widget.h"
#include "../tabs/abstract_tab_deck_editor.h" #include "../tabs/abstract_tab_deck_editor.h"
#include "all_zones_card_amount_widget.h" #include "all_zones_card_amount_widget.h"
@ -12,16 +14,19 @@
#include <QAction> #include <QAction>
#include <QApplication> #include <QApplication>
#include <QColor>
#include <QDebug> #include <QDebug>
#include <QFileDialog> #include <QFileDialog>
#include <QGuiApplication> #include <QGuiApplication>
#include <QImage> #include <QImage>
#include <QImageReader>
#include <QLabel> #include <QLabel>
#include <QList> #include <QList>
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter>
#include <QPalette>
#include <QPen>
#include <QPixmap> #include <QPixmap>
#include <QPixmapCache> #include <QPixmapCache>
#include <QPoint> #include <QPoint>
@ -36,7 +41,6 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QVariant> #include <QVariant>
#include <QtGlobal> #include <QtGlobal>
#include <QtMath>
#include <libcockatrice/card/database/card_database_manager.h> #include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h> #include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/settings/card_override_settings.h> #include <libcockatrice/settings/card_override_settings.h>
@ -120,6 +124,12 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa
mainLayout->addWidget(cardInfoPicture); mainLayout->addWidget(cardInfoPicture);
initializePinBadge(); initializePinBadge();
auto refreshPinBadge = [this] {
initializePinBadge();
updatePinBadgeVisibility();
};
connect(themeManager, &ThemeManager::paletteChanged, this, refreshPinBadge);
connect(themeManager, &ThemeManager::themeChanged, this, refreshPinBadge);
// Parent the preview to this overlay so it is destroyed with it (Qt::ToolTip keeps // Parent the preview to this overlay so it is destroyed with it (Qt::ToolTip keeps
// it a frameless, non-activating top-level window despite the parent). // it a frameless, non-activating top-level window despite the parent).
@ -401,10 +411,55 @@ void PrintingSelectorCardOverlayWidget::customMenu(QPoint point)
} }
/** /**
* @brief Initializes the pin badge overlay and loads its icon with DPI-aware rasterization. * @brief Composes the pin badge: the glyph over a rounded palette backdrop.
* *
* The icon is rasterized once using the label's device pixel ratio so it stays crisp on HiDPI * The badge floats on card art rather than on a palette surface, so a bare glyph
* displays. The resulting pixmap is cached on the QLabel and simply shown/hidden when needed. * colored WindowText is not enough: a thin dark stroke disappears on dark art in a
* light palette, and a thin light stroke disappears on light art in a dark palette.
* A Window-filled rounded rect with a WindowText outline gives the glyph a solid
* backing, and the outline guarantees the badge edge contrasts with the art even when
* the fill matches it. Both roles follow the palette, like the glyph.
*
* @param logicalSize The badge's size in logical pixels.
* @param devicePixelRatio The label's device pixel ratio, applied to the pixmap so it
* stays crisp on HiDPI displays.
* @param palette The palette the glyph's WindowText resolves against.
*/
static QPixmap composePinBadgePixmap(const QSize &logicalSize, qreal devicePixelRatio, const QPalette &palette)
{
// Inset the glyph so the backdrop reads as a badge around it, not as a border.
const int inset = qRound(logicalSize.width() * 0.16);
const QSize glyphSize = logicalSize - QSize(inset * 2, inset * 2);
const QPixmap glyph = loadThemedIcon(QStringLiteral("icons/pin")).pixmap(glyphSize, devicePixelRatio);
if (glyph.isNull()) {
// Let the caller fall back to the textual badge instead of drawing an empty panel.
return QPixmap();
}
QPixmap badge(logicalSize * devicePixelRatio);
badge.setDevicePixelRatio(devicePixelRatio);
badge.fill(Qt::transparent);
QPainter painter(&badge);
painter.setRenderHint(QPainter::Antialiasing);
// Half-pixel inset keeps the 1.5px outline inside the pixmap instead of clipping it.
const QRectF box(QPointF(0.5, 0.5), QSizeF(logicalSize) - QSizeF(1, 1));
painter.setPen(QPen(palette.color(QPalette::WindowText), 1.5));
painter.setBrush(palette.color(QPalette::Window));
painter.drawRoundedRect(box, 8, 8);
painter.drawPixmap(QPointF(inset, inset), glyph);
return badge;
}
/**
* @brief Initializes the pin badge overlay and loads a DPI-aware icon pixmap.
*
* The glyph is drawn onto a rounded palette backdrop so the badge stays readable on
* card art, and it is rasterized at the label's device pixel ratio so it stays crisp on
* HiDPI displays. It is re-rendered when the palette or theme changes so the badge
* follows the surrounding UI instead of freezing at its first colors.
* If the SVG cannot be read, a textual fallback badge is created instead. * If the SVG cannot be read, a textual fallback badge is created instead.
*/ */
void PrintingSelectorCardOverlayWidget::initializePinBadge() void PrintingSelectorCardOverlayWidget::initializePinBadge()
@ -420,25 +475,14 @@ void PrintingSelectorCardOverlayWidget::initializePinBadge()
} }
bool pinLoaded = false; bool pinLoaded = false;
QImageReader pinReader(QStringLiteral("theme:icons/pin"));
if (pinReader.canRead()) {
// Rasterize a 64×64 logical icon so it has a consistent size regardless of card scaling.
const QSize targetSize(64, 64); const QSize targetSize(64, 64);
const qreal dpr = pinBadge->devicePixelRatioF(); const QPixmap pinPix = composePinBadgePixmap(targetSize, pinBadge->devicePixelRatioF(), pinBadge->palette());
const QSize rasterSize(qMax(1, qCeil(targetSize.width() * dpr)), qMax(1, qCeil(targetSize.height() * dpr))); if (!pinPix.isNull()) {
pinReader.setScaledSize(rasterSize);
const QImage pinImage = pinReader.read();
if (!pinImage.isNull()) {
// Tag the pixmap with the same DPR so Qt renders it at the correct physical size.
QPixmap pinPix = QPixmap::fromImage(pinImage);
pinPix.setDevicePixelRatio(dpr);
pinBadge->setPixmap(pinPix); pinBadge->setPixmap(pinPix);
pinBadge->setFixedSize(targetSize); pinBadge->setFixedSize(targetSize);
pinBadge->setStyleSheet(QStringLiteral("background: transparent;")); pinBadge->setStyleSheet(QStringLiteral("background: transparent;"));
pinLoaded = true; pinLoaded = true;
} }
}
if (!pinLoaded) { if (!pinLoaded) {
// Fall back to a text badge when the SVG cannot be decoded (e.g., missing theme resource). // Fall back to a text badge when the SVG cannot be decoded (e.g., missing theme resource).