From df74917cb0d902371ce6eb4d334dbbc9c667ab66 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 4 Nov 2026 18:39:43 +0100 Subject: [PATCH] [PrintingSelector] Load the pin badge through the themed icon engine (#7598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../printing_selector_card_overlay_widget.cpp | 90 ++++++++++++++----- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp index 94acdbdce..a5177a6bc 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp @@ -2,6 +2,8 @@ #include "../../../client/settings/cache_settings.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 "../tabs/abstract_tab_deck_editor.h" #include "all_zones_card_amount_widget.h" @@ -12,16 +14,19 @@ #include #include +#include #include #include #include #include -#include #include #include #include #include #include +#include +#include +#include #include #include #include @@ -36,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -120,6 +124,12 @@ PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *pa mainLayout->addWidget(cardInfoPicture); 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 // 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 - * displays. The resulting pixmap is cached on the QLabel and simply shown/hidden when needed. + * The badge floats on card art rather than on a palette surface, so a bare glyph + * 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. */ void PrintingSelectorCardOverlayWidget::initializePinBadge() @@ -420,24 +475,13 @@ void PrintingSelectorCardOverlayWidget::initializePinBadge() } 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 qreal dpr = pinBadge->devicePixelRatioF(); - const QSize rasterSize(qMax(1, qCeil(targetSize.width() * dpr)), qMax(1, qCeil(targetSize.height() * dpr))); - 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->setFixedSize(targetSize); - pinBadge->setStyleSheet(QStringLiteral("background: transparent;")); - pinLoaded = true; - } + const QSize targetSize(64, 64); + const QPixmap pinPix = composePinBadgePixmap(targetSize, pinBadge->devicePixelRatioF(), pinBadge->palette()); + if (!pinPix.isNull()) { + pinBadge->setPixmap(pinPix); + pinBadge->setFixedSize(targetSize); + pinBadge->setStyleSheet(QStringLiteral("background: transparent;")); + pinLoaded = true; } if (!pinLoaded) {