From b2d14ca101762d7b6c911fb3d8a2d4cd12f6930d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 24 Aug 2025 18:59:41 +0700 Subject: [PATCH] wallpaper selector: thumbnail generation, fix xdg dir folder icons --- .../ii/modules/common/ThumbnailImage.qml | 67 +++++++++++++++++++ .../modules/common/widgets/DirectoryIcon.qml | 4 +- .../WallpaperDirectoryItem.qml | 23 +------ .../WallpaperSelectorContent.qml | 4 +- 4 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 .config/quickshell/ii/modules/common/ThumbnailImage.qml diff --git a/.config/quickshell/ii/modules/common/ThumbnailImage.qml b/.config/quickshell/ii/modules/common/ThumbnailImage.qml new file mode 100644 index 00000000..a1a4c204 --- /dev/null +++ b/.config/quickshell/ii/modules/common/ThumbnailImage.qml @@ -0,0 +1,67 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.modules.common +import qs.modules.common.functions + +/** + * Thumbnail image. + * See Freedesktop's spec: https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html + */ +Image { + id: root + + required property string sourcePath + readonly property var thumbnailSizes: ({ + "normal": 128, + "large": 256, + "x-large": 512, + "xx-large": 1024 + }) + property string thumbnailSizeName: { // https://specifications.freedesktop.org/thumbnail-spec/latest/directory.html + const sizeNames = Object.keys(thumbnailSizes); + for(let i = 0; i < sizeNames.length; i++) { + const sizeName = sizeNames[i]; + const maxSize = thumbnailSizes[sizeName]; + if (root.sourceSize.width <= maxSize && root.sourceSize.height <= maxSize) return sizeName; + } + return "xx-large"; + } + property string thumbnailPath: { + if (sourcePath.length == 0) return; + const resolvedUrl = Qt.resolvedUrl(sourcePath); + const md5Hash = Qt.md5(resolvedUrl); + return `${Directories.genericCache}/thumbnails/${thumbnailSizeName}/${md5Hash}.png`; + } + source: thumbnailPath + + asynchronous: true + cache: false + smooth: true + mipmap: false + + opacity: status === Image.Ready ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + onSourceSizeChanged: { + thumbnailGeneration.running = false + thumbnailGeneration.running = true + } + Process { + id: thumbnailGeneration + command: { + const maxSize = root.thumbnailSizes[root.thumbnailSizeName]; + return ["bash", "-c", + `[ -f '${FileUtils.trimFileProtocol(root.thumbnailPath)}' ] && exit 0 || { magick '${root.sourcePath}' -resize ${maxSize}x${maxSize} '${FileUtils.trimFileProtocol(root.thumbnailPath)}' && exit 1; }` + ] + } + onExited: (exitCode, exitStatus) => { + if (exitCode === 1) { // Force reload if thumbnail had to be generated + root.source = ""; + root.source = root.thumbnailPath; // Force reload + } + } + } +} diff --git a/.config/quickshell/ii/modules/common/widgets/DirectoryIcon.qml b/.config/quickshell/ii/modules/common/widgets/DirectoryIcon.qml index acdb6589..9df2ee2f 100644 --- a/.config/quickshell/ii/modules/common/widgets/DirectoryIcon.qml +++ b/.config/quickshell/ii/modules/common/widgets/DirectoryIcon.qml @@ -2,6 +2,7 @@ import QtQuick import Quickshell import Quickshell.Io import qs.modules.common +import qs.modules.common.functions // From https://github.com/caelestia-dots/shell with modifications. // License: GPLv3 @@ -16,8 +17,7 @@ Image { if (!fileModelData.fileIsDir) return Quickshell.iconPath("application-x-zerosize"); - const homeDir = Directories.home - if ([Directories.documents, Directories.downloads, Directories.music, Directories.pictures, Directories.videos].includes(fileModelData.filePath)) + if ([Directories.documents, Directories.downloads, Directories.music, Directories.pictures, Directories.videos].some(dir => FileUtils.trimFileProtocol(dir) === fileModelData.filePath)) return Quickshell.iconPath(`folder-${fileModelData.fileName.toLowerCase()}`); return Quickshell.iconPath("inode-directory"); diff --git a/.config/quickshell/ii/modules/wallpaperSelector/WallpaperDirectoryItem.qml b/.config/quickshell/ii/modules/wallpaperSelector/WallpaperDirectoryItem.qml index 382880df..2616219c 100644 --- a/.config/quickshell/ii/modules/wallpaperSelector/WallpaperDirectoryItem.qml +++ b/.config/quickshell/ii/modules/wallpaperSelector/WallpaperDirectoryItem.qml @@ -65,34 +65,15 @@ MouseArea { id: thumbnailImageLoader anchors.fill: parent active: root.useThumbnail - sourceComponent: Image { + sourceComponent: ThumbnailImage { id: thumbnailImage - source: { - if (fileModelData.filePath.length == 0) - return; - const resolvedUrl = Qt.resolvedUrl(fileModelData.filePath); - const md5Hash = Qt.md5(resolvedUrl); - const cacheSize = "normal"; - const thumbnailPath = `${Directories.genericCache}/thumbnails/${cacheSize}/${md5Hash}.png`; - return thumbnailPath; - } - asynchronous: true - cache: false - smooth: true - mipmap: false + sourcePath: fileModelData.filePath fillMode: Image.PreserveAspectCrop clip: true sourceSize.width: wallpaperItemColumnLayout.width sourceSize.height: wallpaperItemColumnLayout.height - wallpaperItemColumnLayout.spacing - wallpaperItemName.height - opacity: status === Image.Ready ? 1 : 0 - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - onStatusChanged: if (status === Image.Error) - root.useThumbnail = false - layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml b/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml index f97909bd..11526ed3 100644 --- a/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml +++ b/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml @@ -226,8 +226,8 @@ Item { fileModelData: modelData width: grid.cellWidth height: grid.cellHeight - colBackground: (index === grid?.currentIndex || containsMouse) ? Appearance.colors.colPrimaryContainer : ColorUtils.transparentize(Appearance.colors.colPrimaryContainer) - colText: (index === grid.currentIndex || containsMouse) ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer0 + colBackground: (fileModelData.filePath === Config.options.background.wallpaperPath) ? Appearance.colors.colPrimary : (index === grid?.currentIndex || containsMouse) ? Appearance.colors.colPrimaryContainer : ColorUtils.transparentize(Appearance.colors.colPrimaryContainer) + colText: (fileModelData.filePath === Config.options.background.wallpaperPath) ? Appearance.colors.colOnPrimary : (index === grid.currentIndex || containsMouse) ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer0 onEntered: { grid.currentIndex = index;