summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-08-27 20:32:51 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-08-27 20:32:51 +1000
commit4f60c07e0540f89654b469d134095c37e238d3e8 (patch)
tree6853439cce6933475898c608d4d846fd91fde6f5
parentinternal: move notif icon lower (diff)
downloadcaelestia-shell-4f60c07e0540f89654b469d134095c37e238d3e8.tar.gz
caelestia-shell-4f60c07e0540f89654b469d134095c37e238d3e8.tar.bz2
caelestia-shell-4f60c07e0540f89654b469d134095c37e238d3e8.zip
plugin: create caching image manager
No need for external proc
-rw-r--r--components/filedialog/FolderContents.qml1
-rw-r--r--components/images/CachingIconImage.qml45
-rw-r--r--components/images/CachingImage.qml37
-rw-r--r--plugin/src/Caelestia/CMakeLists.txt1
-rw-r--r--plugin/src/Caelestia/cachingimagemanager.cpp122
-rw-r--r--plugin/src/Caelestia/cachingimagemanager.hpp57
-rw-r--r--plugin/src/Caelestia/cutils.hpp3
7 files changed, 221 insertions, 45 deletions
diff --git a/components/filedialog/FolderContents.qml b/components/filedialog/FolderContents.qml
index 1f67cd3..afd2ed1 100644
--- a/components/filedialog/FolderContents.qml
+++ b/components/filedialog/FolderContents.qml
@@ -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)
diff --git a/components/images/CachingIconImage.qml b/components/images/CachingIconImage.qml
index 522a947..715d379 100644
--- a/components/images/CachingIconImage.qml
+++ b/components/images/CachingIconImage.qml
@@ -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
+ }
}
}
diff --git a/components/images/CachingImage.qml b/components/images/CachingImage.qml
index add459a..07b98b6 100644
--- a/components/images/CachingImage.qml
+++ b/components/images/CachingImage.qml
@@ -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
}
}
diff --git a/plugin/src/Caelestia/CMakeLists.txt b/plugin/src/Caelestia/CMakeLists.txt
index 61d7f7e..3ea0225 100644
--- a/plugin/src/Caelestia/CMakeLists.txt
+++ b/plugin/src/Caelestia/CMakeLists.txt
@@ -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
diff --git a/plugin/src/Caelestia/cachingimagemanager.cpp b/plugin/src/Caelestia/cachingimagemanager.cpp
new file mode 100644
index 0000000..d557203
--- /dev/null
+++ b/plugin/src/Caelestia/cachingimagemanager.cpp
@@ -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();
+}
diff --git a/plugin/src/Caelestia/cachingimagemanager.hpp b/plugin/src/Caelestia/cachingimagemanager.hpp
new file mode 100644
index 0000000..e2cbd41
--- /dev/null
+++ b/plugin/src/Caelestia/cachingimagemanager.hpp
@@ -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;
+};
diff --git a/plugin/src/Caelestia/cutils.hpp b/plugin/src/Caelestia/cutils.hpp
index e1319a4..76beb2b 100644
--- a/plugin/src/Caelestia/cutils.hpp
+++ b/plugin/src/Caelestia/cutils.hpp
@@ -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: