Improve macOS platform integration.
- Allow switching between themes without restart (except classic) - Rework icon loading and recolouring logic to react to theme changes - Automatically react to light/dark theme change - Remove explicit selection of monochrome tray icon variant (selected automatically now) - Update theme background colours for Big Sur - Update application icon to match Big Sur HIG The tray icon doesn't respond perfectly to theme changes yet on Big Sur, since we need different icons for dark and light theme and cannot simply let the OS recolour the icon for us (we do that, too, but only as an additional fallback). At the moment, there is no signal to listen to that would allow this. This patch adds a few generic methods to OSUtils for detecting and communicating theme changes, which are only stubs for Windows and Linux at the moment and need to be implemented in future commits. Fixes #4933 Fixes #5349
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
#include "Icons.h"
|
||||
|
||||
#include <QBitmap>
|
||||
#include <QIconEngine>
|
||||
#include <QPaintDevice>
|
||||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
|
||||
@@ -27,8 +29,24 @@
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/osutils/OSUtils.h"
|
||||
|
||||
class AdaptiveIconEngine : public QIconEngine
|
||||
{
|
||||
public:
|
||||
explicit AdaptiveIconEngine(QIcon baseIcon);
|
||||
void paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) override;
|
||||
QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) override;
|
||||
QIconEngine* clone() const override;
|
||||
|
||||
private:
|
||||
QIcon m_baseIcon;
|
||||
};
|
||||
|
||||
Icons* Icons::m_instance(nullptr);
|
||||
|
||||
Icons::Icons()
|
||||
{
|
||||
}
|
||||
|
||||
QIcon Icons::applicationIcon()
|
||||
{
|
||||
return icon("keepassxc", false);
|
||||
@@ -47,45 +65,102 @@ QString Icons::trayIconAppearance() const
|
||||
return iconAppearance;
|
||||
}
|
||||
|
||||
QIcon Icons::trayIcon()
|
||||
QIcon Icons::trayIcon(QString style)
|
||||
{
|
||||
return trayIconUnlocked();
|
||||
if (style == "unlocked") {
|
||||
style.clear();
|
||||
}
|
||||
if (!style.isEmpty()) {
|
||||
style = "-" + style;
|
||||
}
|
||||
|
||||
auto iconApperance = trayIconAppearance();
|
||||
if (!iconApperance.startsWith("monochrome")) {
|
||||
return icon(QString("keepassxc%1").arg(style), false);
|
||||
}
|
||||
|
||||
QIcon i;
|
||||
#ifdef Q_OS_MACOS
|
||||
if (osUtils->isStatusBarDark()) {
|
||||
i = icon(QString("keepassxc-monochrome-light%1").arg(style), false);
|
||||
} else {
|
||||
i = icon(QString("keepassxc-monochrome-dark%1").arg(style), false);
|
||||
}
|
||||
#else
|
||||
i = icon(QString("keepassxc-%1%2").arg(iconApperance, style), false);
|
||||
#endif
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
||||
// Set as mask to allow the operating system to recolour the tray icon. This may look weird
|
||||
// if we failed to detect the status bar background colour correctly, but it is certainly
|
||||
// better than a barely visible icon and even if we did guess correctly, it allows for better
|
||||
// integration should the system's preferred colours not be 100% black or white.
|
||||
i.setIsMask(true);
|
||||
#endif
|
||||
return i;
|
||||
}
|
||||
|
||||
QIcon Icons::trayIconLocked()
|
||||
{
|
||||
auto iconApperance = trayIconAppearance();
|
||||
|
||||
if (iconApperance == "monochrome-light") {
|
||||
return icon("keepassxc-monochrome-light-locked", false);
|
||||
}
|
||||
if (iconApperance == "monochrome-dark") {
|
||||
return icon("keepassxc-monochrome-dark-locked", false);
|
||||
}
|
||||
return icon("keepassxc-locked", false);
|
||||
return trayIcon("locked");
|
||||
}
|
||||
|
||||
QIcon Icons::trayIconUnlocked()
|
||||
{
|
||||
auto iconApperance = trayIconAppearance();
|
||||
return trayIcon("unlocked");
|
||||
}
|
||||
|
||||
if (iconApperance == "monochrome-light") {
|
||||
return icon("keepassxc-monochrome-light", false);
|
||||
AdaptiveIconEngine::AdaptiveIconEngine(QIcon baseIcon)
|
||||
: QIconEngine()
|
||||
, m_baseIcon(std::move(baseIcon))
|
||||
{
|
||||
}
|
||||
|
||||
void AdaptiveIconEngine::paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
||||
double dpr = !kpxcApp->testAttribute(Qt::AA_UseHighDpiPixmaps) ? 1.0 : painter->device()->devicePixelRatioF();
|
||||
#else
|
||||
double dpr = !kpxcApp->testAttribute(Qt::AA_UseHighDpiPixmaps) ? 1.0 : painter->device()->devicePixelRatio();
|
||||
#endif
|
||||
QSize pixmapSize = rect.size() * dpr;
|
||||
|
||||
painter->save();
|
||||
painter->drawPixmap(rect, m_baseIcon.pixmap(pixmapSize, mode, state));
|
||||
|
||||
if (getMainWindow()) {
|
||||
QPalette palette = getMainWindow()->palette();
|
||||
painter->setCompositionMode(QPainter::CompositionMode_SourceAtop);
|
||||
|
||||
if (mode == QIcon::Active) {
|
||||
painter->fillRect(rect, palette.color(QPalette::Active, QPalette::ButtonText));
|
||||
} else if (mode == QIcon::Selected) {
|
||||
painter->fillRect(rect, palette.color(QPalette::Active, QPalette::HighlightedText));
|
||||
} else if (mode == QIcon::Disabled) {
|
||||
painter->fillRect(rect, palette.color(QPalette::Disabled, QPalette::WindowText));
|
||||
} else {
|
||||
painter->fillRect(rect, palette.color(QPalette::Normal, QPalette::WindowText));
|
||||
}
|
||||
}
|
||||
if (iconApperance == "monochrome-dark") {
|
||||
return icon("keepassxc-monochrome-dark", false);
|
||||
}
|
||||
return icon("keepassxc", false);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QPixmap AdaptiveIconEngine::pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
QImage img(size, QImage::Format_ARGB32_Premultiplied);
|
||||
img.fill(0);
|
||||
QPainter painter(&img);
|
||||
paint(&painter, QRect(0, 0, size.width(), size.height()), mode, state);
|
||||
return QPixmap::fromImage(img, Qt::NoFormatConversion);
|
||||
}
|
||||
|
||||
QIconEngine* AdaptiveIconEngine::clone() const
|
||||
{
|
||||
return new AdaptiveIconEngine(m_baseIcon);
|
||||
}
|
||||
|
||||
QIcon Icons::icon(const QString& name, bool recolor, const QColor& overrideColor)
|
||||
{
|
||||
QIcon icon = m_iconCache.value(name);
|
||||
|
||||
if (!icon.isNull() && !overrideColor.isValid()) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// Resetting the application theme name before calling QIcon::fromTheme() is required for hacky
|
||||
// QPA platform themes such as qt5ct, which randomly mess with the configured icon theme.
|
||||
// If we do not reset the theme name here, it will become empty at some point, causing
|
||||
@@ -94,44 +169,25 @@ QIcon Icons::icon(const QString& name, bool recolor, const QColor& overrideColor
|
||||
// See issue #4963: https://github.com/keepassxreboot/keepassxc/issues/4963
|
||||
// and qt5ct issue #80: https://sourceforge.net/p/qt5ct/tickets/80/
|
||||
QIcon::setThemeName("application");
|
||||
#endif
|
||||
|
||||
QString cacheName =
|
||||
QString("%1:%2:%3").arg(recolor ? "1" : "0", overrideColor.isValid() ? overrideColor.name() : "#", name);
|
||||
QIcon icon = m_iconCache.value(cacheName);
|
||||
|
||||
if (!icon.isNull() && !overrideColor.isValid()) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
icon = QIcon::fromTheme(name);
|
||||
if (getMainWindow() && recolor) {
|
||||
const QRect rect(0, 0, 48, 48);
|
||||
QImage img = icon.pixmap(rect.width(), rect.height()).toImage();
|
||||
img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
icon = {};
|
||||
|
||||
QPainter painter(&img);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
|
||||
|
||||
if (!overrideColor.isValid()) {
|
||||
QPalette palette = getMainWindow()->palette();
|
||||
painter.fillRect(rect, palette.color(QPalette::Normal, QPalette::WindowText));
|
||||
icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal);
|
||||
|
||||
painter.fillRect(rect, palette.color(QPalette::Active, QPalette::ButtonText));
|
||||
icon.addPixmap(QPixmap::fromImage(img), QIcon::Active);
|
||||
|
||||
painter.fillRect(rect, palette.color(QPalette::Active, QPalette::HighlightedText));
|
||||
icon.addPixmap(QPixmap::fromImage(img), QIcon::Selected);
|
||||
|
||||
painter.fillRect(rect, palette.color(QPalette::Disabled, QPalette::WindowText));
|
||||
icon.addPixmap(QPixmap::fromImage(img), QIcon::Disabled);
|
||||
} else {
|
||||
painter.fillRect(rect, overrideColor);
|
||||
icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal);
|
||||
}
|
||||
|
||||
if (recolor) {
|
||||
icon = QIcon(new AdaptiveIconEngine(icon));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
||||
icon.setIsMask(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!overrideColor.isValid()) {
|
||||
m_iconCache.insert(name, icon);
|
||||
}
|
||||
|
||||
m_iconCache.insert(cacheName, icon);
|
||||
return icon;
|
||||
}
|
||||
|
||||
@@ -161,10 +217,6 @@ QIcon Icons::onOffIcon(const QString& name, bool recolor)
|
||||
return icon;
|
||||
}
|
||||
|
||||
Icons::Icons()
|
||||
{
|
||||
}
|
||||
|
||||
Icons* Icons::instance()
|
||||
{
|
||||
if (!m_instance) {
|
||||
|
||||
Reference in New Issue
Block a user