diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Widgets/CachingImage.qml')
| -rw-r--r-- | raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Widgets/CachingImage.qml | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Widgets/CachingImage.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Widgets/CachingImage.qml new file mode 100644 index 0000000..5ffec09 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Widgets/CachingImage.qml @@ -0,0 +1,102 @@ +import QtQuick +import qs.Common + +Item { + id: root + + property string imagePath: "" + property int maxCacheSize: 512 + property int status: isAnimated ? animatedImg.status : staticImg.status + property int fillMode: Image.PreserveAspectCrop + + readonly property bool isRemoteUrl: imagePath.startsWith("http://") || imagePath.startsWith("https://") + readonly property bool isAnimated: { + if (!imagePath) + return false; + const lower = imagePath.toLowerCase(); + return lower.endsWith(".gif") || lower.endsWith(".webp"); + } + readonly property string normalizedPath: { + if (!imagePath) + return ""; + if (isRemoteUrl) + return imagePath; + if (imagePath.startsWith("file://")) + return imagePath.substring(7); + return imagePath; + } + + function djb2Hash(str) { + if (!str) + return ""; + let hash = 5381; + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) + hash) + str.charCodeAt(i); + hash = hash & 0x7FFFFFFF; + } + return hash.toString(16).padStart(8, '0'); + } + + readonly property string imageHash: normalizedPath ? djb2Hash(normalizedPath) : "" + readonly property string cachePath: imageHash && !isRemoteUrl && !isAnimated ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : "" + readonly property string encodedImagePath: { + if (!normalizedPath) + return ""; + if (isRemoteUrl) + return normalizedPath; + return "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/'); + } + + AnimatedImage { + id: animatedImg + anchors.fill: parent + visible: root.isAnimated + asynchronous: true + fillMode: root.fillMode + source: root.isAnimated ? root.imagePath : "" + playing: visible && status === AnimatedImage.Ready + } + + Image { + id: staticImg + anchors.fill: parent + visible: !root.isAnimated + asynchronous: true + fillMode: root.fillMode + sourceSize.width: root.maxCacheSize + sourceSize.height: root.maxCacheSize + smooth: true + + onStatusChanged: { + if (source == root.cachePath && status === Image.Error) { + source = root.encodedImagePath; + return; + } + if (root.isRemoteUrl || source != root.encodedImagePath || status !== Image.Ready || !root.cachePath) + return; + Paths.mkdir(Paths.imagecache); + const grabPath = root.cachePath; + if (visible && width > 0 && height > 0 && Window.window?.visible) { + grabToImage(res => res.saveToFile(grabPath)); + } + } + } + + onImagePathChanged: { + if (!imagePath) { + staticImg.source = ""; + return; + } + if (isAnimated) + return; + if (isRemoteUrl) { + staticImg.source = imagePath; + return; + } + Paths.mkdir(Paths.imagecache); + const hash = djb2Hash(normalizedPath); + const cPath = hash ? `${Paths.stringify(Paths.imagecache)}/${hash}@${maxCacheSize}x${maxCacheSize}.png` : ""; + const encoded = "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/'); + staticImg.source = cPath || encoded; + } +} |