Use SvgRenderer for Unified Search input icons. Refactor IconUtils. Extend unit tests.

Signed-off-by: alex-z <blackslayer4@gmail.com>
This commit is contained in:
alex-z 2021-10-29 10:05:51 +03:00 committed by Matthieu Gallien (Rebase PR Action)
parent 0d8375e798
commit 0b8ab5c079
8 changed files with 239 additions and 79 deletions

View file

@ -121,6 +121,7 @@ set(client_SRCS
userstatusselectormodel.cpp
emojimodel.cpp
fileactivitylistmodel.cpp
tray/svgimageprovider.cpp
tray/syncstatussummary.cpp
tray/activitydata.cpp
tray/activitylistmodel.cpp

View file

@ -1,44 +1,132 @@
/*
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "iconutils.h"
#include <theme.h>
#include <QFile>
#include <QLoggingCategory>
#include <QPainter>
#include <QPixmapCache>
#include <QSvgRenderer>
namespace {
QString findSvgFilePath(const QString &fileName, const QStringList &possibleColors)
{
const QString baseSvgNoColor{QString{OCC::Theme::themePrefix} + fileName};
if (QFile::exists(baseSvgNoColor)) {
return baseSvgNoColor;
}
for (const auto &color : possibleColors) {
const QString baseSVG{QString{OCC::Theme::themePrefix} + color + QLatin1Char('/') + fileName};
if (QFile::exists(baseSVG)) {
return baseSVG;
}
}
return {};
}
}
namespace OCC {
namespace Ui {
namespace IconUtils {
QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundColor)
{
Q_ASSERT(!fileName.isEmpty());
Q_LOGGING_CATEGORY(lcIconUtils, "nextcloud.gui.iconutils", QtInfoMsg)
namespace IconUtils {
QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundColor)
{
Q_ASSERT(!fileName.isEmpty());
// some icons are present in white or black only, so, we need to check both when needed
const auto iconBaseColors = QStringList({ QStringLiteral("black"), QStringLiteral("white") });
const auto pixmapColor = backgroundColor.isValid()
&& !Theme::isDarkColor(backgroundColor)
? QColorConstants::Svg::black
: QColorConstants::Svg::white;
const QString pixmapColor = backgroundColor.isValid() && !Theme::isDarkColor(backgroundColor) ? "black" : "white";
return createSvgPixmapWithCustomColor(fileName, pixmapColor);
}
const QString cacheKey = fileName + QLatin1Char(',') + pixmapColor;
QPixmap createSvgPixmapWithCustomColor(const QString &fileName, const QColor &customColor, const QSize &size)
{
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(customColor.isValid());
QPixmap cachedPixmap;
if (fileName.isEmpty()) {
qWarning(lcIconUtils) << "fileName is empty";
}
if (!customColor.isValid()) {
qWarning(lcIconUtils) << "customColor is invalid";
}
const auto customColorName = customColor.name();
const QString cacheKey = fileName + QLatin1Char(',') + customColorName;
QPixmap cachedPixmap;
// check for existing QPixmap in cache
if (QPixmapCache::find(cacheKey, &cachedPixmap)) {
return cachedPixmap;
}
// some icons are present in white or black only, so, we need to check both when needed
const auto iconBaseColors = QStringList{QStringLiteral("black"), QStringLiteral("white")};
// check if there is an existing pixmap matching the custom color
if (iconBaseColors.contains(customColorName)) {
cachedPixmap = QPixmap::fromImage(QImage{QString{OCC::Theme::themePrefix} + customColorName + QLatin1Char('/') + fileName});
QPixmapCache::insert(cacheKey, cachedPixmap);
return cachedPixmap;
}
// find the first matching svg file
const auto sourceSvg = findSvgFilePath(fileName, iconBaseColors);
Q_ASSERT(!sourceSvg.isEmpty());
if (sourceSvg.isEmpty()) {
qWarning(lcIconUtils) << "Failed to find base SVG file for" << cacheKey;
return {};
}
cachedPixmap = drawSvgWithCustomFillColor(sourceSvg, customColor, size);
Q_ASSERT(!cachedPixmap.isNull());
if (cachedPixmap.isNull()) {
qWarning(lcIconUtils) << "Failed to load pixmap for" << cacheKey;
return {};
}
if (!QPixmapCache::find(cacheKey, &cachedPixmap)) {
if (iconBaseColors.contains(pixmapColor)) {
cachedPixmap = QPixmap::fromImage(QImage(QString(Theme::themePrefix) + pixmapColor + QLatin1Char('/') + fileName));
QPixmapCache::insert(cacheKey, cachedPixmap);
return cachedPixmap;
}
const auto drawSvgWithCustomFillColor = [](const QString &sourceSvgPath, const QString &fillColor) {
QPixmap drawSvgWithCustomFillColor(const QString &sourceSvgPath, const QColor &fillColor, const QSize &size)
{
QSvgRenderer svgRenderer;
if (!svgRenderer.load(sourceSvgPath)) {
return QPixmap();
qCWarning(lcIconUtils) << "Could no load initial SVG image";
return {};
}
const auto requestedSize = size.isValid() ? size : svgRenderer.defaultSize();
// render source image
QImage svgImage(svgRenderer.defaultSize(), QImage::Format_ARGB32);
QImage svgImage(requestedSize, QImage::Format_ARGB32);
{
QPainter svgImagePainter(&svgImage);
svgImage.fill(Qt::GlobalColor::transparent);
@ -46,7 +134,7 @@ QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundCol
}
// draw target image with custom fillColor
QImage image(svgRenderer.defaultSize(), QImage::Format_ARGB32);
QImage image(requestedSize, QImage::Format_ARGB32);
image.fill(QColor(fillColor));
{
QPainter imagePainter(&image);
@ -55,38 +143,7 @@ QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundCol
}
return QPixmap::fromImage(image);
};
// find the first matching svg among base colors, if any
const QString sourceSvg = [&]() {
for (const auto &color : iconBaseColors) {
const QString baseSVG(QString(Theme::themePrefix) + color + QLatin1Char('/') + fileName);
if (QFile(baseSVG).exists()) {
return baseSVG;
}
}
return QString();
}();
Q_ASSERT(!sourceSvg.isEmpty());
if (sourceSvg.isEmpty()) {
qWarning("Failed to find base svg for %s", qPrintable(cacheKey));
return {};
}
cachedPixmap = drawSvgWithCustomFillColor(sourceSvg, pixmapColor);
QPixmapCache::insert(cacheKey, cachedPixmap);
Q_ASSERT(!cachedPixmap.isNull());
if (cachedPixmap.isNull()) {
qWarning("Failed to load pixmap for %s", qPrintable(cacheKey));
return {};
}
}
return cachedPixmap;
}
}
}
}

View file

@ -22,6 +22,8 @@ namespace OCC {
namespace Ui {
namespace IconUtils {
QPixmap pixmapForBackground(const QString &fileName, const QColor &backgroundColor);
QPixmap createSvgPixmapWithCustomColor(const QString &fileName, const QColor &customColor, const QSize &size = {});
QPixmap drawSvgWithCustomFillColor(const QString &sourceSvgPath, const QColor &fillColor, const QSize &size = {});
}
}
}

View file

@ -17,6 +17,7 @@
#include "theme.h"
#include "config.h"
#include "common/utility.h"
#include "tray/svgimageprovider.h"
#include "tray/usermodel.h"
#include "tray/unifiedsearchresultimageprovider.h"
#include "configfile.h"
@ -59,6 +60,7 @@ void Systray::setTrayEngine(QQmlApplicationEngine *trayEngine)
_trayEngine->addImportPath("qrc:/qml/theme");
_trayEngine->addImageProvider("avatars", new ImageProvider);
_trayEngine->addImageProvider(QLatin1String("svgimage-custom-color"), new OCC::Ui::SvgImageProvider);
_trayEngine->addImageProvider(QLatin1String("unified-search-result-icon"), new UnifiedSearchResultImageProvider);
}

View file

@ -46,16 +46,8 @@ TextField {
smooth: true;
antialiasing: true
mipmap: true
source: "qrc:///client/theme/black/search.svg"
source: "image://svgimage-custom-color/search.svg" + "/" + trayWindowUnifiedSearchTextField.textFieldIconsColor
sourceSize: Qt.size(parent.height * parent.textFieldIconsScaleFactor, parent.height * parent.textFieldIconsScaleFactor)
ColorOverlay {
anchors.fill: parent
source: parent
cached: true
color: parent.parent.textFieldIconsColor
}
}
BusyIndicator {
@ -87,17 +79,9 @@ TextField {
mipmap: true
visible: parent.text
source: "qrc:///client/theme/black/clear.svg"
source: "image://svgimage-custom-color/clear.svg" + "/" + trayWindowUnifiedSearchTextField.textFieldIconsColor
sourceSize: Qt.size(parent.height * parent.textFieldIconsScaleFactor, parent.height * parent.textFieldIconsScaleFactor)
ColorOverlay {
anchors.fill: parent
cached: true
source: parent
color: parent.parent.textFieldIconsColor
}
MouseArea {
id: trayWindowUnifiedSearchTextFieldClearTextButtonMouseArea

View file

@ -0,0 +1,53 @@
/*
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "svgimageprovider.h"
#include "iconutils.h"
#include <QLoggingCategory>
namespace OCC {
namespace Ui {
Q_LOGGING_CATEGORY(lcSvgImageProvider, "nextcloud.gui.svgimageprovider", QtInfoMsg)
SvgImageProvider::SvgImageProvider()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
QImage SvgImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
Q_UNUSED(size)
Q_ASSERT(!id.isEmpty());
const auto idSplit = id.split(QLatin1Char('/'), Qt::SkipEmptyParts);
if (idSplit.isEmpty()) {
qCWarning(lcSvgImageProvider) << "Image id is incorrect!";
return {};
}
const auto pixmapName = idSplit.at(0);
const auto pixmapColor = idSplit.size() > 1 ? QColor(idSplit.at(1)) : QColorConstants::Svg::black;
if (pixmapName.isEmpty() || !pixmapColor.isValid()) {
qCWarning(lcSvgImageProvider) << "Image id is incorrect!";
return {};
}
return IconUtils::createSvgPixmapWithCustomColor(pixmapName, pixmapColor, requestedSize).toImage();
}
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#pragma once
#include <QQuickImageProvider>
namespace OCC {
namespace Ui {
class SvgImageProvider : public QQuickImageProvider
{
public:
SvgImageProvider();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
};
}
}

View file

@ -29,12 +29,56 @@ public:
}
private slots:
void testPixmapForBackground()
void testDrawSvgWithCustomFillColor()
{
const QDir blackSvgDir(QString(OCC::Theme::themePrefix) + QStringLiteral("black"));
const QString blackSvgDirPath{QString{OCC::Theme::themePrefix} + QStringLiteral("black")};
const QDir blackSvgDir(blackSvgDirPath);
const QStringList blackImages = blackSvgDir.entryList(QStringList("*.svg"));
const QDir whiteSvgDir(QString(OCC::Theme::themePrefix) + QStringLiteral("white"));
if (!blackImages.isEmpty()) {
QVERIFY(!OCC::Ui::IconUtils::drawSvgWithCustomFillColor(blackSvgDirPath + QLatin1Char('/') + blackImages.at(0), QColorConstants::Svg::red).isNull());
}
if (!blackImages.isEmpty()) {
QVERIFY(!OCC::Ui::IconUtils::drawSvgWithCustomFillColor(blackSvgDirPath + QLatin1Char('/') + blackImages.at(0), QColorConstants::Svg::green).isNull());
}
const QString whiteSvgDirPath{QString{OCC::Theme::themePrefix} + QStringLiteral("white")};
const QDir whiteSvgDir(whiteSvgDirPath);
const QStringList whiteImages = whiteSvgDir.entryList(QStringList("*.svg"));
if (!whiteImages.isEmpty()) {
QVERIFY(!OCC::Ui::IconUtils::drawSvgWithCustomFillColor(whiteSvgDirPath + QLatin1Char('/') + whiteImages.at(0), QColorConstants::Svg::blue).isNull());
}
}
void testCreateSvgPixmapWithCustomColor()
{
const QDir blackSvgDir(QString(QString{OCC::Theme::themePrefix}) + QStringLiteral("black"));
const QStringList blackImages = blackSvgDir.entryList(QStringList("*.svg"));
if (!blackImages.isEmpty()) {
QVERIFY(!OCC::Ui::IconUtils::createSvgPixmapWithCustomColor(blackImages.at(0), QColorConstants::Svg::red).isNull());
}
if (!blackImages.isEmpty()) {
QVERIFY(!OCC::Ui::IconUtils::createSvgPixmapWithCustomColor(blackImages.at(0), QColorConstants::Svg::green).isNull());
}
const QDir whiteSvgDir(QString(QString{OCC::Theme::themePrefix}) + QStringLiteral("white"));
const QStringList whiteImages = whiteSvgDir.entryList(QStringList("*.svg"));
if (!whiteImages.isEmpty()) {
QVERIFY(!OCC::Ui::IconUtils::createSvgPixmapWithCustomColor(whiteImages.at(0), QColorConstants::Svg::blue).isNull());
}
}
void testPixmapForBackground()
{
const QDir blackSvgDir(QString(QString{OCC::Theme::themePrefix}) + QStringLiteral("black"));
const QStringList blackImages = blackSvgDir.entryList(QStringList("*.svg"));
const QDir whiteSvgDir(QString(QString{OCC::Theme::themePrefix}) + QStringLiteral("white"));
const QStringList whiteImages = whiteSvgDir.entryList(QStringList("*.svg"));
if (blackImages.size() > 0) {
@ -46,17 +90,6 @@ private slots:
// black pixmap for bright background - should not fail
QVERIFY(!OCC::Ui::IconUtils::pixmapForBackground(blackImages.at(0), QColor("yellow")).isNull());
}
const auto blackImagesExclusive = QSet<QString>(blackImages.begin(), blackImages.end()).subtract(QSet<QString>(whiteImages.begin(), whiteImages.end()));
const auto whiteImagesExclusive = QSet<QString>(whiteImages.begin(), whiteImages.end()).subtract(QSet<QString>(blackImages.begin(), blackImages.end()));
if (blackImagesExclusive != whiteImagesExclusive) {
// black pixmap for dark background - should fail as we don't have this image in black
QVERIFY(OCC::Ui::IconUtils::pixmapForBackground(blackImagesExclusive.values().at(0), QColor("blue")).isNull());
// white pixmap for bright background - should fail as we don't have this image in white
QVERIFY(OCC::Ui::IconUtils::pixmapForBackground(whiteImagesExclusive.values().at(0), QColor("yellow")).isNull());
}
}
};