1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-09 23:18:31 -04:00

wallpaper: rewrite CachingImage and make it work with file browser

fixes #2756
This commit is contained in:
bbedward
2026-07-05 11:17:21 -04:00
parent 79dfd34ca2
commit b6a27dc713
4 changed files with 198 additions and 160 deletions
@@ -169,8 +169,6 @@ StyledRect {
property string imagePath: { property string imagePath: {
if (weMode && delegateRoot.fileIsDir) if (weMode && delegateRoot.fileIsDir)
return delegateRoot.filePath + "/preview" + weExtensions[weExtIndex]; return delegateRoot.filePath + "/preview" + weExtensions[weExtIndex];
if (!delegateRoot.fileIsDir && isImage)
return delegateRoot.filePath;
if (_videoThumb) if (_videoThumb)
return _videoThumb; return _videoThumb;
return ""; return "";
@@ -192,13 +190,28 @@ StyledRect {
visible: false visible: false
} }
CachingImage {
anchors.fill: parent
anchors.margins: 2
imagePath: !delegateRoot.fileIsDir && isImage ? delegateRoot.filePath : ""
maxCacheSize: 256
visible: !delegateRoot.fileIsDir && isImage
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: gridImageMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
MultiEffect { MultiEffect {
anchors.fill: parent anchors.fill: parent
anchors.margins: 2 anchors.margins: 2
source: gridPreviewImage source: gridPreviewImage
maskEnabled: true maskEnabled: true
maskSource: gridImageMask maskSource: gridImageMask
visible: gridPreviewImage.status === Image.Ready && ((!delegateRoot.fileIsDir && (isImage || isVideo)) || (weMode && delegateRoot.fileIsDir)) visible: gridPreviewImage.status === Image.Ready && ((!delegateRoot.fileIsDir && isVideo) || (weMode && delegateRoot.fileIsDir))
maskThresholdMin: 0.5 maskThresholdMin: 0.5
maskSpreadAtMin: 1 maskSpreadAtMin: 1
} }
@@ -168,13 +168,7 @@ StyledRect {
Image { Image {
id: listPreviewImage id: listPreviewImage
anchors.fill: parent anchors.fill: parent
property string imagePath: { property string imagePath: _videoThumb
if (!listDelegateRoot.fileIsDir && isImage)
return listDelegateRoot.filePath;
if (_videoThumb)
return _videoThumb;
return "";
}
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : "" source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
sourceSize.width: 32 sourceSize.width: 32
@@ -183,12 +177,26 @@ StyledRect {
visible: false visible: false
} }
CachingImage {
anchors.fill: parent
imagePath: !listDelegateRoot.fileIsDir && isImage ? listDelegateRoot.filePath : ""
maxCacheSize: 256
visible: !listDelegateRoot.fileIsDir && isImage
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: listImageMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
MultiEffect { MultiEffect {
anchors.fill: parent anchors.fill: parent
source: listPreviewImage source: listPreviewImage
maskEnabled: true maskEnabled: true
maskSource: listImageMask maskSource: listImageMask
visible: listPreviewImage.status === Image.Ready && !listDelegateRoot.fileIsDir && (isImage || isVideo) visible: listPreviewImage.status === Image.Ready && !listDelegateRoot.fileIsDir && isVideo
maskThresholdMin: 0.5 maskThresholdMin: 0.5
maskSpreadAtMin: 1 maskSpreadAtMin: 1
} }
+72 -43
View File
@@ -20,12 +20,11 @@ Item {
property int itemsPerPage: 16 property int itemsPerPage: 16
property int totalPages: Math.max(1, Math.ceil(wallpaperFolderModel.count / itemsPerPage)) property int totalPages: Math.max(1, Math.ceil(wallpaperFolderModel.count / itemsPerPage))
property bool active: false property bool active: false
property Item focusTarget: wallpaperGrid property Item focusTarget: pager
property Item tabBarItem: null property Item tabBarItem: null
property int gridIndex: 0 property int gridIndex: 0
property Item keyForwardTarget: null property Item keyForwardTarget: null
property var parentPopout: null property var parentPopout: null
property int lastPage: 0
property bool enableAnimation: false property bool enableAnimation: false
property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation) property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation)
property string selectedFileName: "" property string selectedFileName: ""
@@ -86,12 +85,12 @@ Item {
} }
} }
onCurrentPageChanged: { onCurrentPageChanged: updateSelectedFileName()
if (currentPage !== lastPage) {
enableAnimation = false; onTotalPagesChanged: {
lastPage = currentPage; if (currentPage >= totalPages) {
currentPage = Math.max(0, totalPages - 1);
} }
updateSelectedFileName();
} }
onGridIndexChanged: { onGridIndexChanged: {
@@ -257,6 +256,7 @@ Item {
} }
function setInitialSelection() { function setInitialSelection() {
enableAnimation = false;
const currentWallpaper = getCurrentWallpaper(); const currentWallpaper = getCurrentWallpaper();
if (!currentWallpaper || wallpaperFolderModel.count === 0) { if (!currentWallpaper || wallpaperFolderModel.count === 0) {
gridIndex = 0; gridIndex = 0;
@@ -447,22 +447,63 @@ Item {
width: parent.width width: parent.width
height: parent.height - 50 height: parent.height - 50
GridView { ListView {
id: wallpaperGrid id: pager
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - Theme.spacingS width: parent.width - Theme.spacingS
height: parent.height - Theme.spacingS height: parent.height - Theme.spacingS
cellWidth: width / 4 orientation: ListView.Vertical
cellHeight: height / 4 snapMode: ListView.SnapOneItem
highlightRangeMode: ListView.StrictlyEnforceRange
preferredHighlightBegin: 0
preferredHighlightEnd: height
highlightMoveDuration: root.enableAnimation ? Theme.mediumDuration : 0
boundsBehavior: Flickable.StopAtBounds
clip: true clip: true
enabled: root.active enabled: root.active
interactive: root.active interactive: root.active
boundsBehavior: Flickable.StopAtBounds
keyNavigationEnabled: false keyNavigationEnabled: false
activeFocusOnTab: false activeFocusOnTab: false
highlightFollowsCurrentItem: true
highlightMoveDuration: enableAnimation ? Theme.shortDuration : 0
focus: false focus: false
cacheBuffer: Math.max(0, height * 2)
model: root.totalPages
onCurrentIndexChanged: {
if (!moving) {
return;
}
if (currentIndex >= 0 && currentIndex !== root.currentPage) {
root.currentPage = currentIndex;
}
}
Component.onCompleted: currentIndex = root.currentPage
Connections {
target: root
function onCurrentPageChanged() {
if (pager.currentIndex !== root.currentPage) {
pager.currentIndex = root.currentPage;
}
}
}
delegate: GridView {
id: pageGrid
property int pageIndex: index
width: pager.width
height: pager.height
cellWidth: width / 4
cellHeight: height / 4
interactive: false
keyNavigationEnabled: false
activeFocusOnTab: false
focus: false
highlightFollowsCurrentItem: true
highlightMoveDuration: root.enableAnimation ? Theme.shortDuration : 0
currentIndex: root.currentPage === pageIndex ? root.gridIndex : -1
highlight: Item { highlight: Item {
z: 1000 z: 1000
@@ -478,8 +519,8 @@ Item {
model: { model: {
root.gridRevision; // re-evaluate when sort order changes in place root.gridRevision; // re-evaluate when sort order changes in place
const startIndex = currentPage * itemsPerPage; const startIndex = pageIndex * root.itemsPerPage;
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperFolderModel.count); const endIndex = Math.min(startIndex + root.itemsPerPage, wallpaperFolderModel.count);
const items = []; const items = [];
for (var i = startIndex; i < endIndex; i++) { for (var i = startIndex; i < endIndex; i++) {
const filePath = wallpaperFolderModel.get(i, "filePath"); const filePath = wallpaperFolderModel.get(i, "filePath");
@@ -490,39 +531,18 @@ Item {
return items; return items;
} }
onModelChanged: {
const clampedIndex = model.length > 0 ? Math.min(Math.max(0, gridIndex), model.length - 1) : 0;
if (gridIndex !== clampedIndex) {
gridIndex = clampedIndex;
}
}
onCountChanged: { onCountChanged: {
if (count > 0) { if (root.currentPage !== pageIndex || count === 0) {
const clampedIndex = Math.min(gridIndex, count - 1); return;
currentIndex = clampedIndex;
positionViewAtIndex(clampedIndex, GridView.Contain);
}
Qt.callLater(() => {
enableAnimation = true;
});
}
Connections {
target: root
function onGridIndexChanged() {
if (wallpaperGrid.count > 0) {
wallpaperGrid.currentIndex = gridIndex;
if (!enableAnimation) {
wallpaperGrid.positionViewAtIndex(gridIndex, GridView.Contain);
}
} }
if (root.gridIndex >= count) {
root.gridIndex = count - 1;
} }
} }
delegate: Item { delegate: Item {
width: wallpaperGrid.cellWidth width: pageGrid.cellWidth
height: wallpaperGrid.cellHeight height: pageGrid.cellHeight
property string wallpaperPath: modelData || "" property string wallpaperPath: modelData || ""
property bool isSelected: getCurrentWallpaper() === modelData property bool isSelected: getCurrentWallpaper() === modelData
@@ -562,6 +582,7 @@ Item {
anchors.fill: parent anchors.fill: parent
imagePath: modelData || "" imagePath: modelData || ""
maxCacheSize: 256 maxCacheSize: 256
opacity: status === Image.Ready ? 1 : 0
layer.enabled: true layer.enabled: true
layer.effect: MultiEffect { layer.effect: MultiEffect {
@@ -570,6 +591,13 @@ Item {
maskSpreadAtMin: 1.0 maskSpreadAtMin: 1.0
maskSource: maskRect maskSource: maskRect
} }
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }
StateLayer { StateLayer {
@@ -594,6 +622,7 @@ Item {
} }
} }
} }
}
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
+19 -31
View File
@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import Quickshell.Io
import qs.Common import qs.Common
Item { Item {
@@ -9,6 +8,7 @@ Item {
property int maxCacheSize: 512 property int maxCacheSize: 512
property int status: isAnimated ? animatedImg.status : staticImg.status property int status: isAnimated ? animatedImg.status : staticImg.status
property int fillMode: Image.PreserveAspectCrop property int fillMode: Image.PreserveAspectCrop
property bool _fromCache: false
readonly property bool isRemoteUrl: imagePath.startsWith("http://") || imagePath.startsWith("https://") readonly property bool isRemoteUrl: imagePath.startsWith("http://") || imagePath.startsWith("https://")
readonly property bool isAnimated: { readonly property bool isAnimated: {
@@ -69,58 +69,46 @@ Item {
smooth: true smooth: true
onStatusChanged: { onStatusChanged: {
if (source === root.cachePath && status === Image.Error) { switch (status) {
case Image.Error:
if (!root._fromCache)
return;
root._fromCache = false;
source = root.encodedImagePath; source = root.encodedImagePath;
return; return;
} case Image.Ready:
if (root.isRemoteUrl || source !== root.encodedImagePath || status !== Image.Ready || !root.cachePath) if (root._fromCache || root.isRemoteUrl || !root.cachePath)
return;
if (!visible || width <= 0 || height <= 0 || !Window.window?.visible)
return; return;
Paths.mkdir(Paths.imagecache); Paths.mkdir(Paths.imagecache);
const grabPath = root.cachePath; const grabPath = root.cachePath;
if (visible && width > 0 && height > 0 && Window.window?.visible) {
grabToImage(res => res.saveToFile(grabPath)); grabToImage(res => res.saveToFile(grabPath));
}
}
}
Process {
id: cacheProbe
property string cachePath: ""
property string fallbackSource: ""
running: false
command: ["test", "-f", cachePath]
onExited: exitCode => {
if (cacheProbe.cachePath !== root.cachePath)
return; return;
staticImg.source = exitCode === 0 ? cacheProbe.cachePath : cacheProbe.fallbackSource; }
} }
} }
onImagePathChanged: { onImagePathChanged: {
if (!imagePath) { if (!imagePath) {
_fromCache = false;
staticImg.source = ""; staticImg.source = "";
return; return;
} }
if (isAnimated) if (isAnimated)
return; return;
if (isRemoteUrl) { if (isRemoteUrl) {
_fromCache = false;
staticImg.source = imagePath; staticImg.source = imagePath;
return; return;
} }
Paths.mkdir(Paths.imagecache); if (!cachePath) {
const hash = djb2Hash(normalizedPath); _fromCache = false;
const cPath = hash ? `${Paths.stringify(Paths.imagecache)}/${hash}@${maxCacheSize}x${maxCacheSize}.png` : ""; staticImg.source = encodedImagePath;
const encoded = "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
if (!cPath) {
staticImg.source = encoded;
return; return;
} }
cacheProbe.running = false; Paths.mkdir(Paths.imagecache);
cacheProbe.cachePath = cPath; _fromCache = true;
cacheProbe.fallbackSource = encoded; staticImg.source = cachePath;
cacheProbe.running = true;
} }
} }