From d2e163d2e4c388a42a006ddd43c58bbecc3729a9 Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Wed, 12 Jul 2023 14:37:39 +0200 Subject: [PATCH] Add initial implementation of a Windows icon engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement an icon engine for Windows that renders glyphs from the Segoe Fluent Icons font on Windows 11 and the Segoe MDL2 Assets fonts on Windows 10. These fonts are installed on the respective Windows versions by default, and otherwise freely avialable for deployment. Icons from that font will mostly be based on single code points, but as the font is specifically designed to allow combining glyphs by layering, the implementation supports multiple code points as well, and can also use a surrogate pair as well to render e.g. an emoji. Task-number: QTBUG-102346 Change-Id: Ib47a267c3a1878d8f0e00dd954496fc338bb0110 Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/direct2d/CMakeLists.txt | 1 + src/plugins/platforms/windows/CMakeLists.txt | 1 + .../platforms/windows/qwindowsiconengine.cpp | 147 ++++++++++++++++++ .../platforms/windows/qwindowsiconengine.h | 54 +++++++ .../platforms/windows/qwindowstheme.cpp | 9 ++ src/plugins/platforms/windows/qwindowstheme.h | 1 + 6 files changed, 213 insertions(+) create mode 100644 src/plugins/platforms/windows/qwindowsiconengine.cpp create mode 100644 src/plugins/platforms/windows/qwindowsiconengine.h diff --git a/src/plugins/platforms/direct2d/CMakeLists.txt b/src/plugins/platforms/direct2d/CMakeLists.txt index e21b230cd78..fe4a237aff3 100644 --- a/src/plugins/platforms/direct2d/CMakeLists.txt +++ b/src/plugins/platforms/direct2d/CMakeLists.txt @@ -17,6 +17,7 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin ../windows/qwindowscursor.cpp ../windows/qwindowscursor.h ../windows/qwindowsdialoghelpers.cpp ../windows/qwindowsdialoghelpers.h ../windows/qwindowsdropdataobject.cpp ../windows/qwindowsdropdataobject.h + ../windows/qwindowsiconengine.cpp ../windows/qwindowsiconengine.h ../windows/qwindowsinputcontext.cpp ../windows/qwindowsinputcontext.h ../windows/qwindowsintegration.cpp ../windows/qwindowsintegration.h ../windows/qwindowsinternalmimedata.cpp ../windows/qwindowsinternalmimedata.h diff --git a/src/plugins/platforms/windows/CMakeLists.txt b/src/plugins/platforms/windows/CMakeLists.txt index ca1bbcb758c..ea119bfb1e2 100644 --- a/src/plugins/platforms/windows/CMakeLists.txt +++ b/src/plugins/platforms/windows/CMakeLists.txt @@ -22,6 +22,7 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin qwindowsdropdataobject.cpp qwindowsdropdataobject.h qwindowsgdiintegration.cpp qwindowsgdiintegration.h qwindowsgdinativeinterface.cpp qwindowsgdinativeinterface.h + qwindowsiconengine.cpp qwindowsiconengine.h qwindowsinputcontext.cpp qwindowsinputcontext.h qwindowsintegration.cpp qwindowsintegration.h qwindowsinternalmimedata.cpp qwindowsinternalmimedata.h diff --git a/src/plugins/platforms/windows/qwindowsiconengine.cpp b/src/plugins/platforms/windows/qwindowsiconengine.cpp new file mode 100644 index 00000000000..7e355b168bd --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsiconengine.cpp @@ -0,0 +1,147 @@ +// 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 + +#include "qwindowsiconengine.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QWindowsIconEngine::Glyphs QWindowsIconEngine::glyphs() const +{ + if (!QFontInfo(m_iconFont).exactMatch()) + return {}; + + static constexpr std::pair glyphMap[] = { + {u"edit-clear", 0xe894}, + {u"edit-copy", 0xe8c8}, + {u"edit-cut", 0xe8c6}, + {u"edit-delete", 0xe74d}, + {u"edit-find", 0xe721}, + {u"edit-find-replace", Glyphs(0xeb51, 0xeb52)}, + {u"edit-paste", 0xe77f}, + {u"edit-redo", 0xe7a6}, + {u"edit-select-all", 0xe8b3}, + {u"edit-undo", 0xe7a7}, + {u"printer", 0xe749}, + {u"red-heart", Glyphs(0x2764, 0xFE0F)}, + {u"banana", Glyphs(0xffff, 0xD83C, 0xDF4C)}, + }; + + const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){ + return c.first == m_iconName; + }); + return it != std::end(glyphMap) ? it->second : Glyphs(); +} + +namespace { +auto iconFontFamily() +{ + static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11; + return isWindows11 ? u"Segoe Fluent Icons"_s + : u"Segoe MDL2 Assets"_s; +} +} + +QWindowsIconEngine::QWindowsIconEngine(const QString &iconName) + : m_iconName(iconName), m_iconFont(iconFontFamily()) + , m_glyphs(glyphs()) +{ +} + +QWindowsIconEngine::~QWindowsIconEngine() +{} + +QIconEngine *QWindowsIconEngine::clone() const +{ + return new QWindowsIconEngine(m_iconName); +} + +QString QWindowsIconEngine::key() const +{ + return u"QWindowsIconEngine"_s; +} + +QString QWindowsIconEngine::iconName() +{ + return m_iconName; +} + +bool QWindowsIconEngine::isNull() +{ + return m_glyphs.isNull(); +} + +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); + + QPainter painter(&m_pixmap); + QFont renderFont(m_iconFont); + renderFont.setPixelSize(size.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 QRect rect({0, 0}, size); + if (m_glyphs.codepoints[0] == QChar(0xffff)) { + painter.drawText(rect, Qt::AlignCenter, QString(m_glyphs.codepoints + 1, 2)); + } else { + for (const auto &glyph : m_glyphs.codepoints) { + if (glyph.isNull()) + break; + painter.drawText(rect, glyph); + } + } + + m_cacheKey = cacheKey; + } + + return m_pixmap; +} + +void QWindowsIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) +{ + const qreal scale = painter->device()->devicePixelRatio(); + painter->drawPixmap(rect, scaledPixmap(rect.size(), mode, state, scale)); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsiconengine.h b/src/plugins/platforms/windows/qwindowsiconengine.h new file mode 100644 index 00000000000..ac16ff80734 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsiconengine.h @@ -0,0 +1,54 @@ +// 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 QWINDOWSICONENGINE_H +#define QWINDOWSICONENGINE_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWindowsIconEngine : public QIconEngine +{ +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; + +private: + static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state) + { + return (quint64(mode) << 32) | state; + } + struct Glyphs + { + constexpr Glyphs(char16_t g1 = 0, char16_t g2 = 0, char16_t g3 = 0) noexcept + : codepoints{g1, g2, g3} + {} + constexpr bool isNull() const noexcept { return codepoints[0].isNull(); } + const QChar codepoints[3] = {}; + }; + Glyphs glyphs() const; + + const QString m_iconName; + const QFont m_iconFont; + const Glyphs m_glyphs; + mutable QPixmap m_pixmap; + mutable quint64 m_cacheKey = {}; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSICONENGINE_H diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index 4b948b03971..e2ab09afb97 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -7,6 +7,7 @@ #include "qwindowsmenu.h" #include "qwindowsdialoghelpers.h" #include "qwindowscontext.h" +#include "qwindowsiconengine.h" #include "qwindowsintegration.h" #if QT_CONFIG(systemtrayicon) # include "qwindowssystemtrayicon.h" @@ -1174,6 +1175,14 @@ QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOpt return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions)); } +QIconEngine *QWindowsTheme::createIconEngine(const QString &iconName) const +{ + static bool experimentalIconEngines = qEnvironmentVariableIsSet("QT_ENABLE_EXPERIMENTAL_ICON_ENGINES"); + if (experimentalIconEngines) + return new QWindowsIconEngine(iconName); + return nullptr; +} + static inline bool doUseNativeMenus() { const unsigned options = QWindowsIntegration::instance()->options(); diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h index 6a44db8aa76..bc16a9619ef 100644 --- a/src/plugins/platforms/windows/qwindowstheme.h +++ b/src/plugins/platforms/windows/qwindowstheme.h @@ -41,6 +41,7 @@ public: QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override; QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions = {}) const override; + QIconEngine *createIconEngine(const QString &iconName) const override; void windowsThemeChanged(QWindow *window); void displayChanged() { refreshIconPixmapSizes(); }