Keep image picker work bounded for large theme collections

This commit is contained in:
Ric Lewis committed 2026-10-02 22:45:39 -07:00
1 parent a85e29abb5
commit 2ba1015ef2
6 files changed
+240 -33

No files matched your search

+51 -25
View File
@@ -25,6 +25,7 @@ Item {
property bool showLabels: false
property bool filterable: false
property bool layoutSettled: false
property bool neighborImagesEnabled: false
property bool requestActive: false
property int requestSerial: 0
property int applySerial: 0
@@ -51,6 +52,9 @@ Item {
property int sliceSpacing: -30
property int skewOffset: 28
property int bottomChromeHeight: showLabels ? (filterable ? 104 : 74) : (filterable ? 60 : 30)
// Render only what fits on this display, plus one prefetch slice per side.
readonly property int previewRadius: Math.max(1, Math.min(16, Math.ceil((panel.width - expandedWidth) / (2 * (sliceWidth + sliceSpacing))) + 1))
onPreviewRadiusChanged: updateVisibleItems()
onOpenedChanged: if (!opened) layoutSettled = false
@@ -100,14 +104,6 @@ Item {
return ImagePickerModel.firstMatchingIndex(imageArray, filterText)
}
function filteredPosition(index) {
return ImagePickerModel.filteredPosition(imageArray, index, filterText)
}
function selectedFilteredPosition() {
return ImagePickerModel.selectedFilteredPosition(imageArray, selectedIndex, filterText)
}
function select(index, immediate) {
if (imageArray.length === 0) return
if (index < 0) index = 0
@@ -215,8 +211,10 @@ Item {
root.loadedImageRows = rows
root.selectedIndex = root.indexForSelectedImage(newImages)
root.neighborImagesEnabled = false
root.imageArray = newImages
root.imagesLoaded = true
Qt.callLater(root.enableNeighborsWhenReady)
if (reveal !== false) {
root.opened = true
@@ -273,6 +271,22 @@ Item {
}
property var imageArray: []
readonly property var matchingImageIndices: ImagePickerModel.matchingIndices(imageArray, filterText)
onMatchingImageIndicesChanged: updateVisibleItems()
onSelectedIndexChanged: updateVisibleItems()
function updateVisibleItems() {
ImagePickerModel.syncWindow(visibleImages, ImagePickerModel.visibleWindow(matchingImageIndices, selectedIndex, previewRadius))
}
function enableNeighborsWhenReady() {
for (var i = 0; i < imageCards.count; i++) {
var item = imageCards.itemAt(i)
if (item && item.selected && item.previewReady) neighborImagesEnabled = true
}
}
ListModel { id: visibleImages }
function currentThemePreview() {
@@ -465,6 +479,8 @@ Item {
Item {
id: card
visible: root.opened && root.imagesLoaded && root.layoutSettled && root.imageArray.length > 0
opacity: root.layoutSettled ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 90 } }
width: Math.min(parent.width - 80, root.expandedWidth + 13 * (root.sliceWidth + root.sliceSpacing) + 40)
height: root.expandedHeight + Style.space(30) + root.bottomChromeHeight
anchors.centerIn: parent
@@ -515,30 +531,32 @@ Item {
Component.onCompleted: forceActiveFocus()
Repeater {
model: root.imageArray.length
id: imageCards
model: visibleImages
delegate: Item {
id: item
required property int index
required property int imageIndex
required property int relativeIndex
readonly property var imageData: root.imageArray[index]
readonly property var imageData: root.imageArray[imageIndex]
readonly property string filePath: imageData ? imageData.filePath : ""
readonly property string fileName: imageData ? imageData.fileName : ""
readonly property string thumbnailPath: imageData ? imageData.thumbnailPath : ""
readonly property bool matched: root.itemMatches(index)
readonly property int relativeIndex: root.filteredPosition(index) - root.selectedFilteredPosition()
readonly property bool selected: matched && index === root.selectedIndex
readonly property bool nearby: matched && Math.abs(relativeIndex) <= 16
property bool sourceActivated: nearby
onNearbyChanged: if (nearby) sourceActivated = true
readonly property bool selected: imageIndex === root.selectedIndex
readonly property bool previewReady: image.status === Image.Ready || image.status === Image.Error
onSelectedChanged: if (selected && previewReady) root.neighborImagesEnabled = true
visible: nearby
x: selected ? carousel.previewX : (relativeIndex < 0 ? carousel.previewX + relativeIndex * carousel.itemStep : carousel.previewX + root.expandedWidth + root.sliceSpacing + (relativeIndex - 1) * carousel.itemStep)
width: selected ? root.expandedWidth : root.sliceWidth
height: selected ? root.expandedHeight : root.sliceHeight
y: selected ? 0 : (root.expandedHeight - root.sliceHeight) / 2
z: selected ? 100 : 50 - Math.min(Math.abs(relativeIndex), 40)
Behavior on x { NumberAnimation { duration: 90; easing.type: Easing.OutCubic } }
Behavior on y { NumberAnimation { duration: 90; easing.type: Easing.OutCubic } }
Behavior on width { NumberAnimation { duration: 90; easing.type: Easing.OutCubic } }
Behavior on height { NumberAnimation { duration: 90; easing.type: Easing.OutCubic } }
readonly property real skAbs: Math.abs(root.skewOffset)
readonly property real topLeft: root.skewOffset >= 0 ? skAbs : 0
@@ -579,17 +597,25 @@ Item {
maskSpreadAtMin: 0.3
}
Rectangle { anchors.fill: parent; color: root.dimColor }
Image {
id: image
anchors.fill: parent
// Load only the initial/visited nearby images, but keep the
// source once activated so Qt does not tear textures down as
// selection moves through the carousel.
source: item.sourceActivated && item.thumbnailPath ? Util.fileUrl(item.thumbnailPath) : ""
// Even an uncached 6K wallpaper decodes at card size, off the
// GUI thread. Departing cards release their images instead of
// retaining every preview visited in a large collection.
// Queue the selected preview first; neighbors must not delay
// the image the user opened the picker to see.
source: (item.selected || root.neighborImagesEnabled) && item.thumbnailPath ? Util.fileUrl(item.thumbnailPath) : ""
sourceSize: Qt.size(root.expandedWidth, root.expandedHeight)
fillMode: Image.PreserveAspectCrop
asynchronous: false
cache: true
asynchronous: true
cache: false
smooth: true
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 90 } }
onStatusChanged: if (item.selected && (status === Image.Ready || status === Image.Error)) root.neighborImagesEnabled = true
}
Rectangle {
@@ -617,7 +643,7 @@ Item {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: item.selected ? root.applySelected() : root.select(index)
onClicked: item.selected ? root.applySelected() : root.select(item.imageIndex)
}
}
}