diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b7cfb05da88..5fc2e6eb1dc 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -68,6 +68,7 @@ qt_internal_add_module(Gui image/qabstractfileiconprovider.cpp image/qabstractfileiconprovider.h image/qabstractfileiconprovider_p.h image/qbitmap.cpp image/qbitmap.h image/qbmphandler.cpp image/qbmphandler_p.h + image/qfonticonengine.cpp image/qfonticonengine_p.h image/qicon.cpp image/qicon.h image/qicon_p.h image/qiconengine.cpp image/qiconengine.h image/qiconengine_p.h image/qiconengineplugin.cpp image/qiconengineplugin.h diff --git a/src/gui/image/qfonticonengine.cpp b/src/gui/image/qfonticonengine.cpp new file mode 100644 index 00000000000..cca04b66ef9 --- /dev/null +++ b/src/gui/image/qfonticonengine.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qfonticonengine_p.h" + +#ifndef QT_NO_ICON + +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QFontIconEngine::QFontIconEngine(const QString &iconName, const QFont &font) + : m_iconName(iconName) + , m_iconFont(font) +{ +} + +QFontIconEngine::~QFontIconEngine() = default; + +QString QFontIconEngine::iconName() +{ + return m_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); +} + +QList QFontIconEngine::availableSizes(QIcon::Mode, QIcon::State) +{ + return {{16, 16}, {24, 24}, {48, 48}, {128, 128}}; +} + +QSize QFontIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + return QIconEngine::actualSize(size, mode, state); +} + +QPixmap QFontIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + return scaledPixmap(size, mode, state, 1.0); +} + +QPixmap QFontIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) +{ + const quint64 cacheKey = calculateCacheKey(mode, state); + if (cacheKey != m_pixmapCacheKey || m_pixmap.size() != size + || m_pixmap.devicePixelRatio() != scale) { + m_pixmap = QPixmap(size * scale); + m_pixmap.fill(Qt::transparent); + m_pixmap.setDevicePixelRatio(scale); + + if (!m_pixmap.isNull()) { + QPainter painter(&m_pixmap); + paint(&painter, QRect(QPoint(), size), mode, state); + } + + m_pixmapCacheKey = cacheKey; + } + + return m_pixmap; +} + +void QFontIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) +{ + Q_UNUSED(state); + + painter->save(); + QFont renderFont(m_iconFont); + renderFont.setPixelSize(rect.height()); + painter->setFont(renderFont); + + QPalette palette; + switch (mode) { + case QIcon::Active: + painter->setPen(palette.color(QPalette::Active, QPalette::Text)); + break; + case QIcon::Normal: + painter->setPen(palette.color(QPalette::Active, QPalette::Text)); + break; + case QIcon::Disabled: + painter->setPen(palette.color(QPalette::Disabled, QPalette::Text)); + break; + case QIcon::Selected: + painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText)); + break; + } + + const QString text = string(); + + painter->drawText(rect, Qt::AlignCenter, text); + painter->restore(); +} + +QString QFontIconEngine::string() const +{ + return {}; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ICON diff --git a/src/gui/image/qfonticonengine_p.h b/src/gui/image/qfonticonengine_p.h new file mode 100644 index 00000000000..8cc967ec95c --- /dev/null +++ b/src/gui/image/qfonticonengine_p.h @@ -0,0 +1,59 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QFONTICONENGINE_H +#define QFONTICONENGINE_H + +#include + +#ifndef QT_NO_ICON +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QFontIconEngine : public QIconEngine +{ +public: + QFontIconEngine(const QString &iconName, const QFont &font); + ~QFontIconEngine(); + + QString iconName() override; + bool isNull() override; + + QList availableSizes(QIcon::Mode, QIcon::State) override; + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; + QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override; + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; + +protected: + virtual QString string() const; + +private: + static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state) + { + return (quint64(mode) << 32) | state; + } + + const QString m_iconName; + const QFont m_iconFont; + mutable QPixmap m_pixmap; + mutable quint64 m_pixmapCacheKey = {}; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ICON + +#endif // QFONTICONENGINE_H diff --git a/src/plugins/platforms/android/qandroidplatformiconengine.cpp b/src/plugins/platforms/android/qandroidplatformiconengine.cpp index ff96e8fc00d..3d33d747455 100644 --- a/src/plugins/platforms/android/qandroidplatformiconengine.cpp +++ b/src/plugins/platforms/android/qandroidplatformiconengine.cpp @@ -2,6 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qandroidplatformiconengine.h" + +#ifndef QT_NO_ICON + #include "androidjnimain.h" #include @@ -214,13 +217,45 @@ static QString fetchFont(const QString &query) triedFonts[query] = fontFamily; return fontFamily; } + +static QFont selectFont() +{ + QString fontFamily; + // The MaterialIcons-*.ttf and MaterialSymbols* font files are available from + // https://github.com/google/material-design-icons/tree/master. If one of them is + // packaged as a resource with the application, then we use it. We prioritize + // a variable font. + const QStringList fontCandidates = { + "MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf", + "MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf", + "MaterialSymbolsSharp[FILL,GRAD,opsz,wght].ttf", + "MaterialIcons-Regular.ttf", + "MaterialIconsOutlined-Regular.otf", + "MaterialIconsRound-Regular.otf", + "MaterialIconsSharp-Regular.otf", + "MaterialIconsTwoTone-Regular.otf", + }; + for (const auto &fontCandidate : fontCandidates) { + fontFamily = FontProvider::fetchFont(u":/qt-project.org/icons/%1"_s.arg(fontCandidate)); + if (!fontFamily.isEmpty()) + break; + } + + // Otherwise we try to download the Outlined version of Material Symbols + const QString key = qEnvironmentVariable("QT_GOOGLE_FONTS_KEY"); + if (fontFamily.isEmpty() && !key.isEmpty()) + fontFamily = FontProvider::fetchFont(u"key=%1&name=Material+Symbols+Outlined"_s.arg(key)); + + // last resort - use any Material Icons + if (fontFamily.isEmpty()) + fontFamily = u"Material Icons"_s; + return QFont(fontFamily); } -QString QAndroidPlatformIconEngine::glyphs() const -{ - if (!QFontInfo(m_iconFont).exactMatch()) - return {}; +} // namespace FontProvider +static QString getGlyphs(QStringView iconName) +{ static constexpr std::pair glyphMap[] = { {"address-book-new"_L1, u"\ue0e0"}, {"application-exit"_L1, u"\ue5cd"}, @@ -477,139 +512,38 @@ QString QAndroidPlatformIconEngine::glyphs() const {"weather-storm"_L1, u"\uf070"}, }; - const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){ - return c.first == m_iconName; + const auto it = std::find_if(std::begin(glyphMap), + std::end(glyphMap), [iconName](const auto &c){ + return c.first == iconName; }); return it != std::end(glyphMap) ? it->second.toString() - : (m_iconName.length() == 1 ? m_iconName : QString()); + : (iconName.length() == 1 ? iconName.toString() : QString()); } QAndroidPlatformIconEngine::QAndroidPlatformIconEngine(const QString &iconName) - : m_iconName(iconName) - , m_glyphs(glyphs()) + : QFontIconEngine(iconName, FontProvider::selectFont()) + , m_glyphs(getGlyphs(iconName)) { - QString fontFamily; - // The MaterialIcons-*.ttf and MaterialSymbols* font files are available from - // https://github.com/google/material-design-icons/tree/master. If one of them is - // packaged as a resource with the application, then we use it. We prioritize - // a variable font. - const QStringList fontCandidates = { - "MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf", - "MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf", - "MaterialSymbolsSharp[FILL,GRAD,opsz,wght].ttf", - "MaterialIcons-Regular.ttf", - "MaterialIconsOutlined-Regular.otf", - "MaterialIconsRound-Regular.otf", - "MaterialIconsSharp-Regular.otf", - "MaterialIconsTwoTone-Regular.otf", - }; - for (const auto &fontCandidate : fontCandidates) { - fontFamily = FontProvider::fetchFont(u":/qt-project.org/icons/%1"_s.arg(fontCandidate)); - if (!fontFamily.isEmpty()) - break; - } - - // Otherwise we try to download the Outlined version of Material Symbols - const QString key = qEnvironmentVariable("QT_GOOGLE_FONTS_KEY"); - if (fontFamily.isEmpty() && !key.isEmpty()) - fontFamily = FontProvider::fetchFont(u"key=%1&name=Material+Symbols+Outlined"_s.arg(key)); - - // last resort - use any Material Icons - if (fontFamily.isEmpty()) - fontFamily = u"Material Icons"_s; - m_iconFont = QFont(fontFamily); } -QAndroidPlatformIconEngine::~QAndroidPlatformIconEngine() -{} - -QIconEngine *QAndroidPlatformIconEngine::clone() const -{ - return new QAndroidPlatformIconEngine(m_iconName); -} +QAndroidPlatformIconEngine::~QAndroidPlatformIconEngine() = default; QString QAndroidPlatformIconEngine::key() const { return u"QAndroidPlatformIconEngine"_s; } -QString QAndroidPlatformIconEngine::iconName() +QIconEngine *QAndroidPlatformIconEngine::clone() const { - return m_iconName; + QAndroidPlatformIconEngine *that = const_cast(this); + return new QAndroidPlatformIconEngine(that->iconName()); } -bool QAndroidPlatformIconEngine::isNull() +QString QAndroidPlatformIconEngine::string() const { - if (m_glyphs.isEmpty()) - return true; - const QChar c0 = m_glyphs.at(0); - const QFontMetrics fontMetrics(m_iconFont); - if (c0.category() == QChar::Other_Surrogate && m_glyphs.size() > 1) - return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, m_glyphs.at(1))); - return !fontMetrics.inFont(c0); -} - -QList QAndroidPlatformIconEngine::availableSizes(QIcon::Mode, QIcon::State) -{ - return {{16, 16}, {24, 24}, {48, 48}, {128, 128}}; -} - -QSize QAndroidPlatformIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) -{ - return QIconEngine::actualSize(size, mode, state); -} - -QPixmap QAndroidPlatformIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) -{ - return scaledPixmap(size, mode, state, 1.0); -} - -QPixmap QAndroidPlatformIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) -{ - const quint64 cacheKey = calculateCacheKey(mode, state); - if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) { - m_pixmap = QPixmap(size * scale); - m_pixmap.fill(Qt::transparent); - m_pixmap.setDevicePixelRatio(scale); - - if (!m_pixmap.isNull()) { - QPainter painter(&m_pixmap); - paint(&painter, QRect(QPoint(), size), mode, state); - } - - m_cacheKey = cacheKey; - } - - return m_pixmap; -} - -void QAndroidPlatformIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) -{ - Q_UNUSED(state); - - painter->save(); - QFont renderFont(m_iconFont); - renderFont.setPixelSize(rect.height()); - painter->setFont(renderFont); - - QPalette palette; - switch (mode) { - case QIcon::Active: - painter->setPen(palette.color(QPalette::Active, QPalette::Text)); - break; - case QIcon::Normal: - painter->setPen(palette.color(QPalette::Active, QPalette::Text)); - break; - case QIcon::Disabled: - painter->setPen(palette.color(QPalette::Disabled, QPalette::Text)); - break; - case QIcon::Selected: - painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText)); - break; - } - - painter->drawText(rect, Qt::AlignCenter, m_glyphs); - painter->restore(); + return m_glyphs; } QT_END_NAMESPACE + +#endif // QT_NO_ICON diff --git a/src/plugins/platforms/android/qandroidplatformiconengine.h b/src/plugins/platforms/android/qandroidplatformiconengine.h index cac54481d95..0db31e6d825 100644 --- a/src/plugins/platforms/android/qandroidplatformiconengine.h +++ b/src/plugins/platforms/android/qandroidplatformiconengine.h @@ -4,41 +4,30 @@ #ifndef QANDROIDPLATFORMICONENGINE_H #define QANDROIDPLATFORMICONENGINE_H -#include -#include +#include + +#ifndef QT_NO_ICON QT_BEGIN_NAMESPACE -class QAndroidPlatformIconEngine : public QIconEngine +class QAndroidPlatformIconEngine : public QFontIconEngine { public: QAndroidPlatformIconEngine(const QString &iconName); ~QAndroidPlatformIconEngine(); - QIconEngine *clone() const override; - QString key() const override; - QString iconName() override; - bool isNull() override; - QList availableSizes(QIcon::Mode, QIcon::State) override; - QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override; - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; - QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override; - void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; + QString key() const override; + QIconEngine *clone() const override; + +protected: + QString string() const override; private: - static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state) - { - return (quint64(mode) << 32) | state; - } - QString glyphs() const; - - const QString m_iconName; - QFont m_iconFont; const QString m_glyphs; - mutable QPixmap m_pixmap; - mutable quint64 m_cacheKey = {}; }; QT_END_NAMESPACE +#endif // QT_NO_ICON + #endif // QANDROIDPLATFORMICONENGINE_H diff --git a/src/plugins/platforms/windows/qwindowsiconengine.cpp b/src/plugins/platforms/windows/qwindowsiconengine.cpp index 3ce8fb11661..272b56be836 100644 --- a/src/plugins/platforms/windows/qwindowsiconengine.cpp +++ b/src/plugins/platforms/windows/qwindowsiconengine.cpp @@ -3,6 +3,8 @@ #include "qwindowsiconengine.h" +#ifndef QT_NO_ICON + #include #include #include @@ -12,11 +14,8 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -QString QWindowsIconEngine::glyphs() const +static QString getGlyphs(QStringView iconName) { - if (!QFontInfo(m_iconFont).exactMatch()) - return {}; - static constexpr std::pair glyphMap[] = { {"address-book-new"_L1, u"\ue780"}, {"application-exit"_L1, u"\ue8bb"}, @@ -277,16 +276,17 @@ QString QWindowsIconEngine::glyphs() const //{"weather-storm"_L1, u"\uf070"}, }; - const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){ - return c.first == m_iconName; + const auto it = std::find_if(std::begin(glyphMap), + std::end(glyphMap), [iconName](const auto &c){ + return c.first == iconName; }); return it != std::end(glyphMap) ? it->second.toString() - : (m_iconName.length() == 1 ? m_iconName : QString()); + : (iconName.length() == 1 ? iconName.toString() : QString()); } namespace { -auto iconFontFamily() +static auto iconFontFamily() { static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11; return isWindows11 ? u"Segoe Fluent Icons"_s @@ -295,102 +295,30 @@ auto iconFontFamily() } QWindowsIconEngine::QWindowsIconEngine(const QString &iconName) - : m_iconName(iconName), m_iconFont(iconFontFamily()) - , m_glyphs(glyphs()) + : QFontIconEngine(iconName, iconFontFamily()) + , m_glyphs(getGlyphs(iconName)) { } QWindowsIconEngine::~QWindowsIconEngine() {} -QIconEngine *QWindowsIconEngine::clone() const -{ - return new QWindowsIconEngine(m_iconName); -} - QString QWindowsIconEngine::key() const { return u"QWindowsIconEngine"_s; } -QString QWindowsIconEngine::iconName() +QIconEngine *QWindowsIconEngine::clone() const { - return m_iconName; + QWindowsIconEngine *that = const_cast(this); + return new QWindowsIconEngine(that->iconName()); } -bool QWindowsIconEngine::isNull() +QString QWindowsIconEngine::string() const { - if (m_glyphs.isEmpty()) - return true; - - const QChar c0 = m_glyphs.at(0); - const QFontMetrics fontMetrics(m_iconFont); - if (c0.category() == QChar::Other_Surrogate && m_glyphs.size() > 1) - return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, m_glyphs.at(1))); - return !fontMetrics.inFont(c0); -} - -QList QWindowsIconEngine::availableSizes(QIcon::Mode, QIcon::State) -{ - return {{16, 16}, {24, 24}, {48, 48}, {128, 128}}; -} - -QSize QWindowsIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) -{ - return QIconEngine::actualSize(size, mode, state); -} - -QPixmap QWindowsIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) -{ - return scaledPixmap(size, mode, state, 1.0); -} - -QPixmap QWindowsIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) -{ - const quint64 cacheKey = calculateCacheKey(mode, state); - if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) { - m_pixmap = QPixmap(size * scale); - m_pixmap.fill(Qt::transparent); - m_pixmap.setDevicePixelRatio(scale); - - if (!m_pixmap.isNull()) { - QPainter painter(&m_pixmap); - paint(&painter, QRect(QPoint(), size), mode, state); - } - - m_cacheKey = cacheKey; - } - - return m_pixmap; -} - -void QWindowsIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) -{ - Q_UNUSED(state); - - painter->save(); - QFont renderFont(m_iconFont); - renderFont.setPixelSize(rect.height()); - painter->setFont(renderFont); - - QPalette palette; - switch (mode) { - case QIcon::Active: - painter->setPen(palette.color(QPalette::Active, QPalette::Text)); - break; - case QIcon::Normal: - painter->setPen(palette.color(QPalette::Active, QPalette::Text)); - break; - case QIcon::Disabled: - painter->setPen(palette.color(QPalette::Disabled, QPalette::Text)); - break; - case QIcon::Selected: - painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText)); - break; - } - - painter->drawText(rect, Qt::AlignCenter, m_glyphs); - painter->restore(); + return m_glyphs; } QT_END_NAMESPACE + +#endif //QT_NO_ICON diff --git a/src/plugins/platforms/windows/qwindowsiconengine.h b/src/plugins/platforms/windows/qwindowsiconengine.h index 3c6cbddb8b3..57b56f34f65 100644 --- a/src/plugins/platforms/windows/qwindowsiconengine.h +++ b/src/plugins/platforms/windows/qwindowsiconengine.h @@ -4,44 +4,30 @@ #ifndef QWINDOWSICONENGINE_H #define QWINDOWSICONENGINE_H -#include +#include -#include -#include +#ifndef QT_NO_ICON QT_BEGIN_NAMESPACE -class QWindowsIconEngine : public QIconEngine +class QWindowsIconEngine : public QFontIconEngine { public: QWindowsIconEngine(const QString &iconName); ~QWindowsIconEngine(); - QIconEngine *clone() const override; - QString key() const override; - QString iconName() override; - bool isNull() override; - QList availableSizes(QIcon::Mode, QIcon::State) override; - QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override; - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; - QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override; - void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; + QString key() const override; + QIconEngine *clone() const override; + +protected: + QString string() const override; private: - static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state) - { - return (quint64(mode) << 32) | state; - } - - QString glyphs() const; - - const QString m_iconName; - const QFont m_iconFont; const QString m_glyphs; - mutable QPixmap m_pixmap; - mutable quint64 m_cacheKey = {}; }; QT_END_NAMESPACE +#endif // QT_NO_ICON + #endif // QWINDOWSICONENGINE_H