You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dock/src/iconitem.cpp

428 lines
11 KiB
C++

/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: cutefish <cutefishos@foxmail.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 3 of the License, or
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "iconitem.h"
#include <QDebug>
#include <QPainter>
#include <QPaintEngine>
#include <QApplication>
#include <QQuickWindow>
#include <QPixmap>
#include <QImageReader>
#include "managedtexturenode.h"
template<class T>
typename std::enable_if <!std::is_integral<T>(), bool>::type almost_equal(T x, T y, int ulp)
{
return std::abs(x - y) <std::numeric_limits<T>::epsilon() * std::abs(x + y) * ulp
|| std::abs(x - y) <std::numeric_limits<T>::min();
}
class IconItemSource
{
public:
explicit IconItemSource(IconItem *iconItem)
: m_iconItem(iconItem)
{
}
virtual ~IconItemSource()
{
}
virtual bool isValid() const = 0;
virtual const QSize size() const = 0;
virtual QPixmap pixmap(const QSize &size) = 0;
protected:
QQuickWindow *window() {
return m_iconItem->window();
}
IconItem *m_iconItem;
};
class NullSource : public IconItemSource
{
public:
explicit NullSource(IconItem *iconItem)
: IconItemSource(iconItem)
{
}
bool isValid() const override
{
return false;
}
const QSize size() const override
{
return QSize();
}
QPixmap pixmap(const QSize &size) override
{
Q_UNUSED(size)
return QPixmap();
}
};
class QIconSource : public IconItemSource
{
public:
explicit QIconSource(const QIcon &icon, IconItem *iconItem)
: IconItemSource(iconItem)
{
m_icon = icon;
}
bool isValid() const override
{
return !m_icon.isNull();
}
const QSize size() const override
{
return QSize();
}
QPixmap pixmap(const QSize &size) override
{
QPixmap result = m_icon.pixmap(window(), m_icon.actualSize(size));
return result;
}
private:
QIcon m_icon;
};
class QImageSource : public IconItemSource
{
public:
explicit QImageSource(const QImage &imageIcon, IconItem *iconItem)
: IconItemSource(iconItem)
{
m_imageIcon = imageIcon;
}
bool isValid() const override
{
return !m_imageIcon.isNull();
}
const QSize size() const override
{
const QSize s = m_imageIcon.size();
if (s.isValid()) {
return s;
}
return QSize();
}
QPixmap pixmap(const QSize &size) override
{
Q_UNUSED(size)
return QPixmap::fromImage(m_imageIcon);
}
private:
QImage m_imageIcon;
};
class SvgSource : public IconItemSource
{
public:
explicit SvgSource(const QString &sourceString, IconItem *iconItem)
: IconItemSource(iconItem)
{
m_reader.setFileName(sourceString);
}
bool isValid() const override {
return m_reader.canRead();
}
const QSize size() const override {
return QSize();
}
QPixmap pixmap(const QSize &size) override {
m_reader.setScaledSize(size * devicePixelRatio());
return QPixmap::fromImage(m_reader.read());
}
private:
qreal devicePixelRatio() {
return window() ? window()->devicePixelRatio() : qApp->devicePixelRatio();
}
QImageReader m_reader;
QString m_svgIconName;
};
IconItem::IconItem(QQuickItem *parent)
: QQuickItem(parent)
, m_iconItemSource(new NullSource(this))
, m_active(false)
, m_animated(false)
, m_roundToIconSize(true)
, m_textureChanged(false)
, m_sizeChanged(false)
{
setFlag(ItemHasContents, true);
setSmooth(true);
}
void IconItem::setSource(const QVariant &source)
{
if (source == m_source) {
return;
}
const bool oldValid = isValid();
m_source = source;
QString sourceString = source.toString();
// If the QIcon was created with QIcon::fromTheme(), try to load it as svg
if (source.canConvert<QIcon>() && !source.value<QIcon>().name().isEmpty()) {
sourceString = source.value<QIcon>().name();
}
if (!sourceString.isEmpty()) {
// If a file:// URL or a absolute path is passed, take the image pointed by that from disk
QString localFile;
if (sourceString.startsWith(QLatin1String("file:"))) {
localFile = QUrl(sourceString).toLocalFile();
} else if (sourceString.startsWith(QLatin1Char('/'))) {
localFile = sourceString;
} else if (sourceString.startsWith("qrc:/")) {
localFile = sourceString.remove(0, 3);
} else if (sourceString.startsWith(":/")) {
localFile = sourceString;
}
if (!localFile.isEmpty()) {
if (sourceString.endsWith(QLatin1String(".svg"))
|| sourceString.endsWith(QLatin1String(".svgz"))
|| sourceString.endsWith(QLatin1String(".ico"))) {
QIcon icon = QIcon(localFile);
m_iconItemSource.reset(new QIconSource(icon, this));
} else {
QImage imageIcon = QImage(localFile);
m_iconItemSource.reset(new QImageSource(imageIcon, this));
}
} else {
// if (sourceString.startsWith("qrc:/"))
// m_iconItemSource.reset(new SvgSource(sourceString.remove(0, 3), this));
// else if (sourceString.startsWith(":/"))
// m_iconItemSource.reset(new SvgSource(sourceString, this));
if (!m_iconItemSource->isValid()) {
// if we started with a QIcon use that.
QIcon icon = source.value<QIcon>();
if (icon.isNull()) {
icon = QIcon::fromTheme(sourceString, QIcon::fromTheme("application-x-desktop"));
}
m_iconItemSource.reset(new QIconSource(icon, this));
}
}
} else if (source.canConvert<QIcon>()) {
m_iconItemSource.reset(new QIconSource(source.value<QIcon>(), this));
} else if (source.canConvert<QImage>()) {
m_iconItemSource.reset(new QImageSource(source.value<QImage>(), this));
} else {
m_iconItemSource.reset(new NullSource(this));
}
if (width() > 0 && height() > 0) {
schedulePixmapUpdate();
}
updateImplicitSize();
emit sourceChanged();
if (isValid() != oldValid) {
Q_EMIT validChanged();
}
}
QVariant IconItem::source() const
{
return m_source;
}
void IconItem::updateImplicitSize()
{
if (m_iconItemSource->isValid()) {
const QSize s = m_iconItemSource->size();
if (s.isValid()) {
if (!m_implicitWidthSetByUser && !m_implicitHeightSetByUser) {
setImplicitSize(s.width(), s.height());
} else if (!m_implicitWidthSetByUser) {
setImplicitWidth(s.width());
} else if (!m_implicitHeightSetByUser) {
setImplicitHeight(s.height());
}
return;
}
}
// Fall back to initializing implicit size to the Dialog size.
const int implicitSize = 16;
if (!m_implicitWidthSetByUser && !m_implicitHeightSetByUser) {
setImplicitSize(implicitSize, implicitSize);
} else if (!m_implicitWidthSetByUser) {
setImplicitWidth(implicitSize);
} else if (!m_implicitHeightSetByUser) {
setImplicitHeight(implicitSize);
}
}
bool IconItem::isValid() const
{
return m_iconItemSource->isValid();
}
int IconItem::paintedWidth() const
{
return boundingRect().size().toSize().width();
}
int IconItem::paintedHeight() const
{
return boundingRect().size().toSize().height();
}
void IconItem::updateIcon()
{
updatePolish();
}
void IconItem::updatePolish()
{
QQuickItem::updatePolish();
loadPixmap();
}
QSGNode *IconItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
{
Q_UNUSED(updatePaintNodeData)
if (m_iconPixmap.isNull() || width() == 0.0 || height() == 0.0) {
delete oldNode;
return nullptr;
}
ManagedTextureNode *textureNode = dynamic_cast<ManagedTextureNode *>(oldNode);
if (!textureNode || m_textureChanged) {
delete oldNode;
textureNode = new ManagedTextureNode;
textureNode->setTexture(QSharedPointer<QSGTexture>(window()->createTextureFromImage(m_iconPixmap.toImage(), QQuickWindow::TextureCanUseAtlas)));
m_sizeChanged = true;
m_textureChanged = false;
}
textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
if (m_sizeChanged) {
const QSize newSize = QSize(paintedWidth(), paintedHeight());
const QRect destRect(QPointF(boundingRect().center() - QPointF(newSize.width(), newSize.height()) / 2).toPoint(), newSize);
textureNode->setRect(destRect);
m_sizeChanged = false;
}
return textureNode;
}
void IconItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
QQuickItem::itemChange(change, value);
}
void IconItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
if (newGeometry.size() != oldGeometry.size()) {
m_sizeChanged = true;
if (newGeometry.width() > 1 && newGeometry.height() > 1) {
schedulePixmapUpdate();
} else {
update();
}
const auto oldSize = qMin(oldGeometry.size().width(), oldGeometry.size().height());
const auto newSize = qMin(newGeometry.size().width(), newGeometry.size().height());
if (!almost_equal(oldSize, newSize, 2)) {
emit paintedSizeChanged();
}
}
QQuickItem::geometryChanged(newGeometry, oldGeometry);
}
void IconItem::componentComplete()
{
QQuickItem::componentComplete();
schedulePixmapUpdate();
}
void IconItem::schedulePixmapUpdate()
{
polish();
}
void IconItem::loadPixmap()
{
if (!isComponentComplete()) {
return;
}
int size = qMin(qRound(width()), qRound(height()));
QPixmap result;
if (size <= 0) {
m_iconPixmap = QPixmap();
update();
return;
}
if (m_iconItemSource->isValid()) {
result = m_iconItemSource->pixmap(QSize(size * qApp->devicePixelRatio(),
size * qApp->devicePixelRatio()));
result.setDevicePixelRatio(qApp->devicePixelRatio());
} else {
m_iconPixmap = QPixmap();
update();
return;
}
m_oldIconPixmap = m_iconPixmap;
m_iconPixmap = result;
m_textureChanged = true;
update();
}