Cockatrice/cockatrice/src/interface/widgets/general/home_styled_button.cpp
Bruno Alexandre Rosa 9e98a6386d Fill the home buttons' outlined text with the winding rule
The Home tab buttons draw their label as a QPainterPath: a black stroke,
then a white fill. Upstream #7539 (2026-11-04) moved the label from the
stylesheet's sans-serif/Helvetica Neue to the theme's InterfaceDefault
font, which on macOS is the system font, SF Pro. Its glyph outlines have
overlapping contours, and QPainterPath fills with Qt::OddEvenFill by
default, so every overlap stayed unfilled and showed the black stroke
underneath as dark notches inside the letters. The same symptom was
upstream issue 6490, fixed in January for the old font.

Qt::WindingFill fills overlapping contours. Checked on macOS with
Homebrew Qt 6.12: the notches are gone.
2026-11-12 05:57:02 -07:00

126 lines
4.5 KiB
C++

#include "home_styled_button.h"
#include "../../theme_manager.h"
#include <QBrush>
#include <QFont>
#include <QFontMetrics>
#include <QPainter>
#include <QPainterPath>
#include <QPen>
#include <QPointF>
#include <QSize>
#include <QSizePolicy>
#include <QStyle>
#include <QStyleOptionButton>
#include <QtGlobal>
#include <qnamespace.h>
class QWidget;
HomeStyledButton::HomeStyledButton(const QString &text, QPair<QColor, QColor> _gradientColors, QWidget *parent)
: QPushButton(text, parent), gradientColors(_gradientColors)
{
// Keep the font/style-derived height even when the home page has ample space.
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setStyleSheet(generateButtonStylesheet(gradientColors));
applyInterfaceDefaultFont();
connect(themeManager, &ThemeManager::fontsChanged, this, &HomeStyledButton::applyInterfaceDefaultFont);
}
void HomeStyledButton::applyInterfaceDefaultFont()
{
// Family comes from the InterfaceDefault theme role; the 34px size and
// bold weight previously carried by the stylesheet stay caller-side.
QFont buttonFont = themeManager->appFont(AppFont::InterfaceDefault);
buttonFont.setPixelSize(34);
buttonFont.setWeight(QFont::Bold);
setFont(buttonFont);
}
void HomeStyledButton::updateStylesheet(const QPair<QColor, QColor> &colors)
{
gradientColors = colors;
setStyleSheet(generateButtonStylesheet(gradientColors));
}
QString HomeStyledButton::generateButtonStylesheet(const QPair<QColor, QColor> &colors)
{
QColor baseGradientStart = colors.first;
QColor baseGradientEnd = colors.second;
QColor hoverGradientStart = baseGradientStart.lighter(120); // 20% lighter
QColor hoverGradientEnd = baseGradientEnd.lighter(120);
QColor pressedGradientStart = baseGradientStart.darker(130); // 30% darker
QColor pressedGradientEnd = baseGradientEnd.darker(130);
// Disabled: more gray, less saturated
QColor disabledGradientStart = baseGradientStart.toHsv();
disabledGradientStart.setHsv(disabledGradientStart.hue(), disabledGradientStart.saturation() * 0.2,
disabledGradientStart.value() * 0.6);
QColor disabledGradientEnd = baseGradientEnd.toHsv();
disabledGradientEnd.setHsv(disabledGradientEnd.hue(), disabledGradientEnd.saturation() * 0.2,
disabledGradientEnd.value() * 0.6);
return QString(R"(
QPushButton {
padding: 30px;
color: white;
border: 2px solid %1;
border-radius: 20px;
background: qlineargradient(x1:0, y1:1, x2:0, y2:0,
stop:0 %2, stop:1 %3);
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:1, x2:0, y2:0,
stop:0 %4, stop:1 %5);
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 %6, stop:1 %7);
}
QPushButton:disabled {
color: #aaaaaa;
border: 2px solid #888888;
background: qlineargradient(x1:0, y1:1, x2:0, y2:0,
stop:0 %8, stop:1 %9);
}
)")
.arg(baseGradientStart.name())
.arg(baseGradientStart.name())
.arg(baseGradientEnd.name())
.arg(hoverGradientStart.name())
.arg(hoverGradientEnd.name())
.arg(pressedGradientStart.name())
.arg(pressedGradientEnd.name())
.arg(disabledGradientStart.name())
.arg(disabledGradientEnd.name());
}
void HomeStyledButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event); // Event is just used for update clipping, we redraw the whole widget.
QStyleOptionButton opt;
initStyleOption(&opt);
opt.text.clear(); // prevent style from drawing text
QPainter painter(this);
style()->drawControl(QStyle::CE_PushButton, &opt, &painter, this);
// Draw white text with a black outline
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::TextAntialiasing);
QFontMetrics fm(font());
QSize textSize = fm.size(Qt::TextSingleLine, text());
QPointF center((width() - textSize.width()) / 2.0, (height() + textSize.height() / 2.0) / 2.0);
QPainterPath path;
path.addText(center, font(), text());
path.setFillRule(Qt::WindingFill);
QPen pen(Qt::black, 4.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.strokePath(path, pen);
painter.fillPath(path, Qt::white);
}