Add initial implementation of a Windows icon engine

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ø <tor.arne.vestbo@qt.io>
This commit is contained in:
Volker Hilsheimer 2023-07-12 14:37:39 +02:00
parent 5ae6355487
commit d2e163d2e4
6 changed files with 213 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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 <QtCore/qoperatingsystemversion.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpainter.h>
#include <QtGui/qpalette.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
QWindowsIconEngine::Glyphs QWindowsIconEngine::glyphs() const
{
if (!QFontInfo(m_iconFont).exactMatch())
return {};
static constexpr std::pair<QStringView, Glyphs> 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<QSize> 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

View File

@ -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 <QtCore/qt_windows.h>
#include <QtGui/qfont.h>
#include <QtGui/qiconengine.h>
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<QSize> 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

View File

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

View File

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