diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e8b938d..1ab7321 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,8 @@ set(SOURCES blurhelper/windowblur.cpp blurhelper/windowblur.h windowhelper.cpp + iconitem.cpp + managedtexturenode.cpp meuikit.qrc meui-style/style.qrc diff --git a/src/iconitem.cpp b/src/iconitem.cpp new file mode 100644 index 0000000..40b3751 --- /dev/null +++ b/src/iconitem.cpp @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: cutefish + * + * 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 . + */ + +#include "iconitem.h" +#include +#include +#include +#include +#include +#include +#include + +#include "managedtexturenode.h" + +template +typename std::enable_if (), bool>::type almost_equal(T x, T y, int ulp) +{ + return std::abs(x - y) ::epsilon() * std::abs(x + y) * ulp + || std::abs(x - y) ::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() && !source.value().name().isEmpty()) { + sourceString = source.value().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(); + if (icon.isNull()) { + icon = QIcon::fromTheme(sourceString, QIcon::fromTheme("application-x-desktop")); + } + m_iconItemSource.reset(new QIconSource(icon, this)); + } + } + + } else if (source.canConvert()) { + m_iconItemSource.reset(new QIconSource(source.value(), this)); + } else if (source.canConvert()) { + m_iconItemSource.reset(new QImageSource(source.value(), 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(oldNode); + + if (!textureNode || m_textureChanged) { + delete oldNode; + textureNode = new ManagedTextureNode; + textureNode->setTexture(QSharedPointer(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(); +} diff --git a/src/iconitem.h b/src/iconitem.h new file mode 100644 index 0000000..bc69a4b --- /dev/null +++ b/src/iconitem.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: cutefish + * + * 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 . + */ + +#ifndef ICONITEM_H +#define ICONITEM_H + +#include +#include +#include +#include + +#include + +class IconItemSource; +class IconItem : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(bool valid READ isValid NOTIFY validChanged) + Q_PROPERTY(int paintedWidth READ paintedWidth NOTIFY paintedSizeChanged) + Q_PROPERTY(int paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) + +public: + explicit IconItem(QQuickItem *parent = nullptr); + + void setSource(const QVariant &source); + QVariant source() const; + + void updateImplicitSize(); + + bool isValid() const; + + int paintedWidth() const; + int paintedHeight() const; + + Q_INVOKABLE void updateIcon(); + + void updatePolish() override; + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override; + + void itemChange(ItemChange change, const ItemChangeData &value) override; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + void componentComplete() override; + +signals: + void overlaysChanged(); + void activeChanged(); + void sourceChanged(); + void animatedChanged(); + void roundToIconSizeChanged(); + void validChanged(); + void colorGroupChanged(); + void paintedSizeChanged(); + void statusChanged(); + void implicitHeightChanged2(); + void implicitWidthChanged2(); + +private slots: + void schedulePixmapUpdate(); + +private: + void loadPixmap(); + +private: + QSharedPointer m_iconItemSource; + QVariant m_source; + + bool m_active; + bool m_animated; + bool m_roundToIconSize; + + bool m_textureChanged; + bool m_sizeChanged; + bool m_allowNextAnimation; + bool m_blockNextAnimation; + bool m_implicitHeightSetByUser; + bool m_implicitWidthSetByUser; + + QPixmap m_iconPixmap; + QPixmap m_oldIconPixmap; +}; + +#endif // ICONITEM_H diff --git a/src/managedtexturenode.cpp b/src/managedtexturenode.cpp new file mode 100644 index 0000000..90d9efb --- /dev/null +++ b/src/managedtexturenode.cpp @@ -0,0 +1,16 @@ +/* + SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "managedtexturenode.h" + +ManagedTextureNode::ManagedTextureNode() +{ +} + +void ManagedTextureNode::setTexture(QSharedPointer texture) +{ + m_texture = texture; + QSGSimpleTextureNode::setTexture(texture.data()); +} diff --git a/src/managedtexturenode.h b/src/managedtexturenode.h new file mode 100644 index 0000000..3b08ddf --- /dev/null +++ b/src/managedtexturenode.h @@ -0,0 +1,27 @@ +/* + SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef MANAGEDTEXTURENODE_H +#define MANAGEDTEXTURENODE_H + +#include +#include +#include +#include + +class ManagedTextureNode : public QSGSimpleTextureNode +{ + Q_DISABLE_COPY(ManagedTextureNode) + +public: + ManagedTextureNode(); + + void setTexture(QSharedPointer texture); + +private: + QSharedPointer m_texture; +}; + +#endif diff --git a/src/meuikit.cpp b/src/meuikit.cpp index 7f1c4b7..cf8e00a 100644 --- a/src/meuikit.cpp +++ b/src/meuikit.cpp @@ -23,6 +23,7 @@ #include "shadowhelper/windowshadow.h" #include "blurhelper/windowblur.h" #include "windowhelper.h" +#include "iconitem.h" #include #include @@ -52,6 +53,7 @@ void MeuiKit::registerTypes(const char *uri) qmlRegisterType(uri, 1, 0, "WindowShadow"); qmlRegisterType(uri, 1, 0, "WindowBlur"); qmlRegisterType(uri, 1, 0, "WindowHelper"); + qmlRegisterType(uri, 1, 0, "IconItem"); qmlRegisterSingletonType(componentUrl(QStringLiteral("Theme.qml")), uri, 1, 0, "Theme"); qmlRegisterSingletonType(componentUrl(QStringLiteral("Units.qml")), uri, 1, 0, "Units");