diff --git a/Commons/Settings.qml b/Commons/Settings.qml index eed8bbe..8e00047 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -17,6 +17,7 @@ Singleton { "HOME") + "/.config") + "/" + shellName + "/" property string cacheDir: Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env( "HOME") + "/.cache") + "/" + shellName + "/" + property string cacheDirImages: cacheDir + "images/" property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json") @@ -37,6 +38,7 @@ Singleton { // ensure settings dir exists Quickshell.execDetached(["mkdir", "-p", configDir]) Quickshell.execDetached(["mkdir", "-p", cacheDir]) + Quickshell.execDetached(["mkdir", "-p", cacheDirImages]) } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index e0412e0..e0eb3bb 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -135,27 +135,32 @@ Item { delegate: Rectangle { id: wallpaperItem + property string wallpaperPath: modelData property bool isSelected: wallpaperPath === WallpaperService.currentWallpaper width: wallpaperGridView.itemSize height: Math.floor(wallpaperGridView.itemSize * 0.67) - radius: Style.radiusMedium * scaling - color: isSelected ? Color.mPrimary : Color.mSurface - border.color: isSelected ? Color.mSecondary : Color.mOutline - border.width: Math.max(1, Style.borderThin * scaling) - clip: true + color: Color.transparent - NImageRounded { - anchors.fill: parent - anchors.margins: Style.marginTiny * scaling + // NImageCached relies on the image being visible to work properly. + // MultiEffect relies on the image being invisible to apply effects. + // That's why we don't have rounded corners here, as we don't want to bring back qt5compat. + NImageCached { + id: img imagePath: wallpaperPath - fallbackIcon: "image" - - imageRadius: Style.radiusMedium * scaling + anchors.fill: parent } - // Selection indicator + // Borders on top + Rectangle { + anchors.fill: parent + color: Color.transparent + border.color: isSelected ? Color.mPrimary : Color.mOutline + border.width: Math.max(1, Style.borderThick * scaling) + } + + // Selection tick-mark Rectangle { anchors.top: parent.top anchors.right: parent.right diff --git a/Widgets/NImageCached.qml b/Widgets/NImageCached.qml new file mode 100644 index 0000000..3980370 --- /dev/null +++ b/Widgets/NImageCached.qml @@ -0,0 +1,59 @@ +pragma ComponentBehavior + +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Commons + +Image { + id: root + + property string imagePath: "" + property string imageHash: "" + property int maxCacheDimension: 512 + readonly property string cachePath: imageHash ? `${Settings.cacheDirImages}${imageHash}@${maxCacheDimension}x${maxCacheDimension}.png` : "" + + asynchronous: true + fillMode: Image.PreserveAspectCrop + sourceSize.width: maxCacheDimension + sourceSize.height: maxCacheDimension + smooth: true + onImagePathChanged: { + if (imagePath) { + hashProcess.command = ["sha256sum", imagePath] + hashProcess.running = true + } else { + source = "" + imageHash = "" + } + } + onCachePathChanged: { + if (imageHash && cachePath) { + // Try to load the cached version, failure will be detected below in onStatusChanged + source = cachePath + //Logger.Log(imagePath, cachePath) + } + } + onStatusChanged: { + if (source == cachePath && status === Image.Error) { + // Cached image was not available, show the original + source = imagePath + } else if (source == imagePath && status === Image.Ready && imageHash && cachePath) { + // Original image is shown and fully loaded, time to cache it + const grabPath = cachePath + if (visible && width > 0 && height > 0 && Window.window && Window.window.visible) + grabToImage(res => { + return res.saveToFile(grabPath) + }) + } + } + + Process { + id: hashProcess + stdout: StdioCollector { + onStreamFinished: { + root.imageHash = text.split(" ")[0] + } + } + } +}