QFontIconEngine: render named glyphs as icons, if possible
If the QIcon::themeName matches an installed font, and if the name of the icon matches a named glyph in the font, then render that glyph as a painter path. Overrides of QFontIconEngine::text() take priority. Amend the manual test to allow specifying an icon theme on the command line, and render the named glyph also as text, as some icon fonts will define ligatures that turn the string into the corresponding icon. Task-number: QTBUG-102346 Change-Id: I788c6274322359955cbfe58175a2999a57cfce95 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
parent
2af58490b3
commit
b3788c7bfc
@ -11,8 +11,12 @@
|
||||
|
||||
#include <QtGui/qfontdatabase.h>
|
||||
#include <QtGui/qpainter.h>
|
||||
#include <QtGui/qpainterpath.h>
|
||||
#include <QtGui/qpalette.h>
|
||||
|
||||
#include <QtGui/private/qfont_p.h>
|
||||
#include <QtGui/private/qfontengine_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
@ -25,6 +29,16 @@ QFontIconEngine::QFontIconEngine(const QString &iconName, const QFont &font)
|
||||
|
||||
QFontIconEngine::~QFontIconEngine() = default;
|
||||
|
||||
QIconEngine *QFontIconEngine::clone() const
|
||||
{
|
||||
return new QFontIconEngine(m_iconName, m_iconFont);
|
||||
}
|
||||
|
||||
QString QFontIconEngine::key() const
|
||||
{
|
||||
return u"QFontIconEngine("_s + m_iconFont.key() + u')';
|
||||
}
|
||||
|
||||
QString QFontIconEngine::iconName()
|
||||
{
|
||||
return m_iconName;
|
||||
@ -33,13 +47,15 @@ QString QFontIconEngine::iconName()
|
||||
bool QFontIconEngine::isNull()
|
||||
{
|
||||
const QString text = string();
|
||||
if (text.isEmpty())
|
||||
return true;
|
||||
const QChar c0 = text.at(0);
|
||||
const QFontMetrics fontMetrics(m_iconFont);
|
||||
if (c0.category() == QChar::Other_Surrogate && text.size() > 1)
|
||||
return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, text.at(1)));
|
||||
return !fontMetrics.inFont(c0);
|
||||
if (!text.isEmpty()) {
|
||||
const QChar c0 = text.at(0);
|
||||
const QFontMetrics fontMetrics(m_iconFont);
|
||||
if (c0.isHighSurrogate() && text.size() > 1)
|
||||
return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, text.at(1)));
|
||||
return !fontMetrics.inFont(c0);
|
||||
}
|
||||
|
||||
return glyph() == 0;
|
||||
}
|
||||
|
||||
QList<QSize> QFontIconEngine::availableSizes(QIcon::Mode, QIcon::State)
|
||||
@ -84,27 +100,57 @@ void QFontIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mo
|
||||
painter->save();
|
||||
QFont renderFont(m_iconFont);
|
||||
renderFont.setPixelSize(rect.height());
|
||||
painter->setFont(renderFont);
|
||||
|
||||
QColor color = Qt::black;
|
||||
QPalette palette;
|
||||
switch (mode) {
|
||||
case QIcon::Active:
|
||||
painter->setPen(palette.color(QPalette::Active, QPalette::Text));
|
||||
color = palette.color(QPalette::Active, QPalette::Text);
|
||||
break;
|
||||
case QIcon::Normal:
|
||||
painter->setPen(palette.color(QPalette::Active, QPalette::Text));
|
||||
color = palette.color(QPalette::Active, QPalette::Text);
|
||||
break;
|
||||
case QIcon::Disabled:
|
||||
painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
|
||||
color = palette.color(QPalette::Disabled, QPalette::Text);
|
||||
break;
|
||||
case QIcon::Selected:
|
||||
painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText));
|
||||
color = palette.color(QPalette::Active, QPalette::HighlightedText);
|
||||
break;
|
||||
}
|
||||
|
||||
const QString text = string();
|
||||
if (!text.isEmpty()) {
|
||||
painter->setFont(renderFont);
|
||||
painter->setPen(color);
|
||||
painter->drawText(rect, Qt::AlignCenter, text);
|
||||
} else if (glyph_t glyphIndex = glyph()) {
|
||||
QFontEngine *engine = QFontPrivate::get(renderFont)->engineForScript(QChar::Script_Common);
|
||||
|
||||
painter->drawText(rect, Qt::AlignCenter, text);
|
||||
const glyph_metrics_t gm = engine->boundingBox(glyphIndex);
|
||||
const int glyph_x = qFloor(gm.x.toReal());
|
||||
const int glyph_y = qFloor(gm.y.toReal());
|
||||
const int glyph_width = qCeil((gm.x + gm.width).toReal()) - glyph_x;
|
||||
const int glyph_height = qCeil((gm.y + gm.height).toReal()) - glyph_y;
|
||||
|
||||
QPainterPath path;
|
||||
if (glyph_width > 0 && glyph_height > 0) {
|
||||
QFixedPoint pt(QFixed(-glyph_x), QFixed(-glyph_y));
|
||||
path.setFillRule(Qt::WindingFill);
|
||||
engine->addGlyphsToPath(&glyphIndex, &pt, 1, &path, {});
|
||||
// make the glyph fit tightly into rect
|
||||
const QRectF pathBoundingRect = path.boundingRect();
|
||||
// center the glyph inside the rect
|
||||
const QPointF topLeft = rect.topLeft() - pathBoundingRect.topLeft()
|
||||
+ (QPointF(rect.width(), rect.height())
|
||||
- QPointF(pathBoundingRect.width(), pathBoundingRect.height())) / 2;
|
||||
painter->translate(topLeft);
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(color);
|
||||
painter->drawPath(path);
|
||||
}
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
@ -113,6 +159,16 @@ QString QFontIconEngine::string() const
|
||||
return {};
|
||||
}
|
||||
|
||||
glyph_t QFontIconEngine::glyph() const
|
||||
{
|
||||
if (m_glyph == uninitializedGlyph) {
|
||||
QFontEngine *engine = QFontPrivate::get(m_iconFont)->engineForScript(QChar::Script_Common);
|
||||
if (engine)
|
||||
m_glyph = engine->findGlyph(QLatin1StringView(m_iconName.toLatin1()));
|
||||
}
|
||||
return m_glyph;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QT_NO_ICON
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using glyph_t = quint32;
|
||||
|
||||
class Q_GUI_EXPORT QFontIconEngine : public QIconEngine
|
||||
{
|
||||
public:
|
||||
@ -30,6 +32,8 @@ public:
|
||||
|
||||
QString iconName() override;
|
||||
bool isNull() override;
|
||||
QString key() const override;
|
||||
QIconEngine *clone() const override;
|
||||
|
||||
QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
|
||||
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
|
||||
@ -39,6 +43,7 @@ public:
|
||||
|
||||
protected:
|
||||
virtual QString string() const;
|
||||
virtual glyph_t glyph() const;
|
||||
|
||||
private:
|
||||
static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
|
||||
@ -50,6 +55,8 @@ private:
|
||||
const QFont m_iconFont;
|
||||
mutable QPixmap m_pixmap;
|
||||
mutable quint64 m_pixmapCacheKey = {};
|
||||
static constexpr glyph_t uninitializedGlyph = std::numeric_limits<glyph_t>::max();
|
||||
mutable glyph_t m_glyph = uninitializedGlyph;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <QtGui/QIconEnginePlugin>
|
||||
#include <QtGui/QPixmapCache>
|
||||
#include <qpa/qplatformtheme.h>
|
||||
#include <QtGui/qfontdatabase.h>
|
||||
#include <QtGui/QPalette>
|
||||
#include <QtCore/qmath.h>
|
||||
#include <QtCore/QList>
|
||||
@ -21,6 +22,7 @@
|
||||
|
||||
#include <private/qhexstring_p.h>
|
||||
#include <private/qfactoryloader_p.h>
|
||||
#include <private/qfonticonengine_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -665,8 +667,21 @@ QIconEngine *QIconLoader::iconEngine(const QString &iconName) const
|
||||
if (m_factory && *m_factory)
|
||||
iconEngine.reset(m_factory.value()->create(iconName));
|
||||
|
||||
if (hasUserTheme() && (!iconEngine || iconEngine->isNull()))
|
||||
iconEngine.reset(new QIconLoaderEngine(iconName));
|
||||
if (hasUserTheme()) {
|
||||
if (!iconEngine || iconEngine->isNull()) {
|
||||
if (QFontDatabase::families().contains(themeName())) {
|
||||
QFont maybeIconFont(themeName());
|
||||
maybeIconFont.setStyleStrategy(QFont::NoFontMerging);
|
||||
qCDebug(lcIconLoader) << "Trying font icon engine.";
|
||||
iconEngine.reset(new QFontIconEngine(iconName, maybeIconFont));
|
||||
}
|
||||
}
|
||||
if (!iconEngine || iconEngine->isNull()) {
|
||||
qCDebug(lcIconLoader) << "Trying loader engine for theme.";
|
||||
iconEngine.reset(new QIconLoaderEngine(iconName));
|
||||
}
|
||||
}
|
||||
|
||||
if (!iconEngine || iconEngine->isNull()) {
|
||||
qCDebug(lcIconLoader) << "Icon is not available from theme or fallback theme.";
|
||||
if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtCore/QCommandLineParser>
|
||||
|
||||
#include <QtWidgets/private/qapplication_p.h>
|
||||
#include <QtGui/qpa/qplatformtheme.h>
|
||||
@ -497,9 +498,15 @@ public:
|
||||
connect(lineEdit, &QLineEdit::textChanged,
|
||||
this, &IconInspector::updateIcon);
|
||||
|
||||
button = new QToolButton;
|
||||
button->setCheckable(true);
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
QHBoxLayout *hbox = new QHBoxLayout;
|
||||
vbox->addStretch(10);
|
||||
vbox->addWidget(lineEdit);
|
||||
hbox->addWidget(lineEdit);
|
||||
hbox->addWidget(button);
|
||||
vbox->addLayout(hbox);
|
||||
setLayout(vbox);
|
||||
}
|
||||
|
||||
@ -508,6 +515,18 @@ protected:
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.fillRect(event->rect(), palette().window());
|
||||
|
||||
// some fonts use icon names as ligatures
|
||||
if (const QString themeName = QIcon::themeName(); !themeName.isEmpty()) {
|
||||
const QFont themeFont(themeName, 24);
|
||||
if (QFontInfo(themeFont).family() == themeName) {
|
||||
painter.save();
|
||||
painter.setFont(themeFont);
|
||||
painter.drawText(rect(), icon.name());
|
||||
painter.restore();
|
||||
}
|
||||
}
|
||||
|
||||
if (!icon.isNull()) {
|
||||
const QString modeLabels[] = { u"Normal"_s, u"Disabled"_s, u"Active"_s, u"Selected"_s};
|
||||
const QString stateLabels[] = { u"On"_s, u"Off"_s};
|
||||
@ -555,10 +574,12 @@ protected:
|
||||
QFrame::paintEvent(event);
|
||||
}
|
||||
private:
|
||||
QToolButton *button;
|
||||
QIcon icon;
|
||||
void updateIcon(const QString &iconName)
|
||||
{
|
||||
icon = QIcon::fromTheme(iconName);
|
||||
button->setIcon(icon);
|
||||
update();
|
||||
}
|
||||
};
|
||||
@ -567,6 +588,21 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QApplication::setApplicationVersion(QT_VERSION_STR);
|
||||
QApplication::setApplicationName(QLatin1String("IconBrowser Manual Test"));
|
||||
QApplication::setOrganizationName(QLatin1String("QtProject"));
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QApplication::applicationName());
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
QCommandLineOption themeOption({u"theme"_s, u"t"_s},
|
||||
u"The name of the icon theme"_s, u"theme"_s);
|
||||
parser.addOption(themeOption);
|
||||
parser.process(app);
|
||||
if (const QString theme = parser.value(themeOption); !theme.isEmpty())
|
||||
QIcon::setThemeName(theme);
|
||||
|
||||
#ifdef ICONBROWSER_RESOURCE
|
||||
Q_INIT_RESOURCE(icons);
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user