mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-25 14:36:01 +03:00
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:
parent
0d8375e798
commit
0b8ab5c079
8 changed files with 239 additions and 79 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
53
src/gui/tray/svgimageprovider.cpp
Normal file
53
src/gui/tray/svgimageprovider.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
28
src/gui/tray/svgimageprovider.h
Normal file
28
src/gui/tray/svgimageprovider.h
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue