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(); }