plugin: create caching image manager
No need for external proc
This commit is contained in:
parent
8311eed7a6
commit
4f60c07e05
7 changed files with 221 additions and 45 deletions
|
|
@ -148,7 +148,6 @@ Item {
|
|||
anchors.top: parent.top
|
||||
anchors.topMargin: Appearance.padding.normal
|
||||
|
||||
asynchronous: true
|
||||
implicitSize: Sizes.itemWidth - Appearance.padding.normal * 2
|
||||
source: {
|
||||
if (!item.fileIsDir)
|
||||
|
|
|
|||
|
|
@ -1,31 +1,42 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.utils
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
property alias asynchronous: image.asynchronous
|
||||
property alias status: image.status
|
||||
property alias mipmap: image.mipmap
|
||||
property alias backer: image
|
||||
id: root
|
||||
|
||||
property real implicitSize
|
||||
readonly property int status: loader.item?.status ?? Image.Null
|
||||
readonly property real actualSize: Math.min(width, height)
|
||||
|
||||
property real implicitSize
|
||||
property url source
|
||||
|
||||
onSourceChanged: {
|
||||
if (source?.toString().startsWith("image://icon/"))
|
||||
// Directly skip the path prop and treat like a normal Image component
|
||||
image.source = source;
|
||||
else if (source)
|
||||
image.path = source;
|
||||
}
|
||||
|
||||
implicitWidth: implicitSize
|
||||
implicitHeight: implicitSize
|
||||
|
||||
CachingImage {
|
||||
id: image
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceComponent: root.source ? root.source.toString().startsWith("image://icon/") ? iconImage : cachingImage : null
|
||||
}
|
||||
|
||||
Component {
|
||||
id: cachingImage
|
||||
|
||||
CachingImage {
|
||||
path: Paths.strip(root.source)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: iconImage
|
||||
|
||||
IconImage {
|
||||
source: root.source
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,29 @@
|
|||
import qs.utils
|
||||
import Caelestia
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Image {
|
||||
id: root
|
||||
|
||||
property string path
|
||||
property string hash
|
||||
readonly property url cachePath: `${Paths.imagecache}/${hash}@${effectiveWidth}x${effectiveHeight}.png`
|
||||
property alias path: manager.path
|
||||
|
||||
readonly property real effectiveScale: QsWindow.window?.devicePixelRatio ?? 1
|
||||
readonly property int effectiveWidth: Math.ceil(width * effectiveScale)
|
||||
readonly property int effectiveHeight: Math.ceil(height * effectiveScale)
|
||||
property int sourceWidth
|
||||
property int sourceHeight
|
||||
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
sourceSize.width: effectiveWidth
|
||||
sourceSize.height: effectiveHeight
|
||||
|
||||
onPathChanged: shaProc.exec(["sha256sum", Paths.strip(path)])
|
||||
|
||||
onCachePathChanged: {
|
||||
if (hash)
|
||||
source = cachePath;
|
||||
}
|
||||
sourceSize.width: sourceWidth
|
||||
sourceSize.height: sourceHeight
|
||||
|
||||
onStatusChanged: {
|
||||
if (source == cachePath && status === Image.Error)
|
||||
source = path;
|
||||
else if (source == path && status === Image.Ready)
|
||||
CUtils.saveItem(this, cachePath);
|
||||
if (!manager.usingCache && status === Image.Ready)
|
||||
CUtils.saveItem(this, manager.cachePath);
|
||||
}
|
||||
|
||||
Process {
|
||||
id: shaProc
|
||||
CachingImageManager {
|
||||
id: manager
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root.hash = text.split(" ")[0]
|
||||
}
|
||||
item: root
|
||||
cacheDir: Paths.imagecache
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ qt_add_qml_module(caelestia
|
|||
VERSION 0.1
|
||||
SOURCES
|
||||
cutils.hpp cutils.cpp
|
||||
cachingimagemanager.hpp cachingimagemanager.cpp
|
||||
)
|
||||
|
||||
qt_query_qml_module(caelestia
|
||||
|
|
|
|||
122
plugin/src/Caelestia/cachingimagemanager.cpp
Normal file
122
plugin/src/Caelestia/cachingimagemanager.cpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#include "cachingimagemanager.hpp"
|
||||
|
||||
#include <qobject.h>
|
||||
#include <QtQuick/QQuickItem>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QCryptographicHash>
|
||||
#include <QThreadPool>
|
||||
#include <QFile>
|
||||
|
||||
qreal CachingImageManager::effectiveScale() const {
|
||||
if (m_item->window() && m_item->window()->screen()) {
|
||||
return m_item->window()->screen()->devicePixelRatio();
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
int CachingImageManager::effectiveWidth() const {
|
||||
int width = std::ceil(m_item->width() * effectiveScale());
|
||||
m_item->setProperty("sourceWidth", width);
|
||||
return width;
|
||||
}
|
||||
|
||||
int CachingImageManager::effectiveHeight() const {
|
||||
int height = std::ceil(m_item->height() * effectiveScale());
|
||||
m_item->setProperty("sourceHeight", height);
|
||||
return height;
|
||||
}
|
||||
|
||||
QQuickItem* CachingImageManager::item() const {
|
||||
return m_item;
|
||||
}
|
||||
|
||||
void CachingImageManager::setItem(QQuickItem* item) {
|
||||
if (m_item == item) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_item = item;
|
||||
emit itemChanged();
|
||||
}
|
||||
|
||||
QUrl CachingImageManager::cacheDir() const {
|
||||
return m_cacheDir;
|
||||
}
|
||||
|
||||
void CachingImageManager::setCacheDir(const QUrl& cacheDir) {
|
||||
if (m_cacheDir == cacheDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_cacheDir = cacheDir;
|
||||
emit cacheDirChanged();
|
||||
}
|
||||
|
||||
QString CachingImageManager::path() const {
|
||||
return m_path;
|
||||
}
|
||||
|
||||
void CachingImageManager::setPath(const QString& path) {
|
||||
if (m_path == path) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_path = path;
|
||||
emit pathChanged();
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
QThreadPool::globalInstance()->start([path, this] {
|
||||
const QString filename = QString("%1@%2x%3.png")
|
||||
.arg(sha256sum(path))
|
||||
.arg(effectiveWidth())
|
||||
.arg(effectiveHeight());
|
||||
|
||||
m_cachePath = m_cacheDir.resolved(QUrl(filename));
|
||||
emit cachePathChanged();
|
||||
|
||||
if (!m_cachePath.isLocalFile()) {
|
||||
qWarning() << "CachingImageManager::setPath: cachePath" << m_cachePath << "is not a local file";
|
||||
return;
|
||||
}
|
||||
|
||||
if (QFile::exists(m_cachePath.toLocalFile())) {
|
||||
QMetaObject::invokeMethod(m_item, [this]() {
|
||||
m_item->setProperty("source", m_cachePath);
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
m_usingCache = true;
|
||||
emit usingCacheChanged();
|
||||
} else {
|
||||
QMetaObject::invokeMethod(m_item, [path, this]() {
|
||||
m_item->setProperty("source", QUrl::fromLocalFile(path));
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
m_usingCache = false;
|
||||
emit usingCacheChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QUrl CachingImageManager::cachePath() const {
|
||||
return m_cachePath;
|
||||
}
|
||||
|
||||
bool CachingImageManager::usingCache() const {
|
||||
return m_usingCache;
|
||||
}
|
||||
|
||||
QString CachingImageManager::sha256sum(const QString& path) const {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "CachingImageManager::sha256sum: failed to open" << path;
|
||||
return "";
|
||||
}
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Sha256);
|
||||
hash.addData(&file);
|
||||
file.close();
|
||||
|
||||
return hash.result().toHex();
|
||||
}
|
||||
57
plugin/src/Caelestia/cachingimagemanager.hpp
Normal file
57
plugin/src/Caelestia/cachingimagemanager.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
class CachingImageManager : public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
|
||||
Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED);
|
||||
Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED);
|
||||
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
|
||||
Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged);
|
||||
Q_PROPERTY(bool usingCache READ usingCache NOTIFY usingCacheChanged);
|
||||
|
||||
public:
|
||||
explicit CachingImageManager(QObject* parent = nullptr): QObject(parent) {};
|
||||
|
||||
[[nodiscard]] QQuickItem* item() const;
|
||||
void setItem(QQuickItem* item);
|
||||
|
||||
[[nodiscard]] QUrl cacheDir() const;
|
||||
void setCacheDir(const QUrl& cacheDir);
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(const QString& path);
|
||||
|
||||
[[nodiscard]] QUrl cachePath() const;
|
||||
[[nodiscard]] bool usingCache() const;
|
||||
|
||||
signals:
|
||||
void itemChanged();
|
||||
void cacheDirChanged();
|
||||
|
||||
void pathChanged();
|
||||
void cachePathChanged();
|
||||
void usingCacheChanged();
|
||||
|
||||
private slots:
|
||||
void handleStatusChanged();
|
||||
|
||||
private:
|
||||
QQuickItem* m_item;
|
||||
QUrl m_cacheDir;
|
||||
|
||||
QString m_path;
|
||||
QUrl m_cachePath;
|
||||
bool m_usingCache;
|
||||
|
||||
[[nodiscard]] qreal effectiveScale() const;
|
||||
int effectiveWidth() const;
|
||||
int effectiveHeight() const;
|
||||
|
||||
[[nodiscard]] QString sha256sum(const QString& path) const;
|
||||
};
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
class CUtils : public QObject {
|
||||
Q_OBJECT;
|
||||
QML_NAMED_ELEMENT(CUtils);
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue