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.top: parent.top
|
||||||
anchors.topMargin: Appearance.padding.normal
|
anchors.topMargin: Appearance.padding.normal
|
||||||
|
|
||||||
asynchronous: true
|
|
||||||
implicitSize: Sizes.itemWidth - Appearance.padding.normal * 2
|
implicitSize: Sizes.itemWidth - Appearance.padding.normal * 2
|
||||||
source: {
|
source: {
|
||||||
if (!item.fileIsDir)
|
if (!item.fileIsDir)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,42 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import qs.utils
|
||||||
|
import Quickshell.Widgets
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property alias asynchronous: image.asynchronous
|
id: root
|
||||||
property alias status: image.status
|
|
||||||
property alias mipmap: image.mipmap
|
|
||||||
property alias backer: image
|
|
||||||
|
|
||||||
property real implicitSize
|
readonly property int status: loader.item?.status ?? Image.Null
|
||||||
readonly property real actualSize: Math.min(width, height)
|
readonly property real actualSize: Math.min(width, height)
|
||||||
|
property real implicitSize
|
||||||
property url source
|
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
|
implicitWidth: implicitSize
|
||||||
implicitHeight: implicitSize
|
implicitHeight: implicitSize
|
||||||
|
|
||||||
CachingImage {
|
Loader {
|
||||||
id: image
|
id: loader
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
sourceComponent: root.source ? root.source.toString().startsWith("image://icon/") ? iconImage : cachingImage : null
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: cachingImage
|
||||||
|
|
||||||
|
CachingImage {
|
||||||
|
path: Paths.strip(root.source)
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: iconImage
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
source: root.source
|
||||||
|
asynchronous: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,29 @@
|
||||||
import qs.utils
|
import qs.utils
|
||||||
import Caelestia
|
import Caelestia
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string path
|
property alias path: manager.path
|
||||||
property string hash
|
|
||||||
readonly property url cachePath: `${Paths.imagecache}/${hash}@${effectiveWidth}x${effectiveHeight}.png`
|
|
||||||
|
|
||||||
readonly property real effectiveScale: QsWindow.window?.devicePixelRatio ?? 1
|
property int sourceWidth
|
||||||
readonly property int effectiveWidth: Math.ceil(width * effectiveScale)
|
property int sourceHeight
|
||||||
readonly property int effectiveHeight: Math.ceil(height * effectiveScale)
|
|
||||||
|
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
sourceSize.width: effectiveWidth
|
sourceSize.width: sourceWidth
|
||||||
sourceSize.height: effectiveHeight
|
sourceSize.height: sourceHeight
|
||||||
|
|
||||||
onPathChanged: shaProc.exec(["sha256sum", Paths.strip(path)])
|
|
||||||
|
|
||||||
onCachePathChanged: {
|
|
||||||
if (hash)
|
|
||||||
source = cachePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (source == cachePath && status === Image.Error)
|
if (!manager.usingCache && status === Image.Ready)
|
||||||
source = path;
|
CUtils.saveItem(this, manager.cachePath);
|
||||||
else if (source == path && status === Image.Ready)
|
|
||||||
CUtils.saveItem(this, cachePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
CachingImageManager {
|
||||||
id: shaProc
|
id: manager
|
||||||
|
|
||||||
stdout: StdioCollector {
|
item: root
|
||||||
onStreamFinished: root.hash = text.split(" ")[0]
|
cacheDir: Paths.imagecache
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ qt_add_qml_module(caelestia
|
||||||
VERSION 0.1
|
VERSION 0.1
|
||||||
SOURCES
|
SOURCES
|
||||||
cutils.hpp cutils.cpp
|
cutils.hpp cutils.cpp
|
||||||
|
cachingimagemanager.hpp cachingimagemanager.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_query_qml_module(caelestia
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
#include <QtQuick/QQuickItem>
|
#include <QtQuick/QQuickItem>
|
||||||
|
|
||||||
class CUtils : public QObject {
|
class CUtils : public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
QML_NAMED_ELEMENT(CUtils);
|
QML_ELEMENT;
|
||||||
QML_SINGLETON;
|
QML_SINGLETON;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue