Keep image picker work bounded for large theme collections
This commit is contained in:
1 parent
a85e29abb5
commit
2ba1015ef2
6 files changed
+240
-33
No files matched your search
@@ -77,6 +77,8 @@ clears it without writing a selection.
|
||||
The plugin has `keepLoaded: true` so the layer-shell window survives
|
||||
between summons within a single shell session.
|
||||
|
||||
The carousel renders only the slices that fit on the screen plus one prefetch slice per side, capped at 33 cards. It preserves overlapping delegates during navigation, decodes images asynchronously at card size, and loads the selected preview before its neighbors. Lazy thumbnail preparation runs below normal CPU and I/O priority in one shared worker pool per image list: one worker on single/dual-core machines and at most two on larger machines. Opening and navigating the picker does not wait for that queue to finish.
|
||||
|
||||
## Lock screen
|
||||
|
||||
Session-lock surface using Quickshell's native `WlSessionLock` and two
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,45 @@ function nextSelectedIndexForFilter(images, selectedIndex, filterText) {
|
||||
return firstMatchingIndex(images, filterText)
|
||||
}
|
||||
|
||||
function matchingIndices(images, filterText) {
|
||||
var indices = []
|
||||
for (var i = 0; i < images.length; i++) {
|
||||
if (itemMatches(images, i, filterText)) indices.push(i)
|
||||
}
|
||||
return indices
|
||||
}
|
||||
|
||||
// Keep the rendered carousel independent of the size of the collection.
|
||||
function visibleWindow(indices, selectedIndex, radius) {
|
||||
var position = indices.indexOf(selectedIndex)
|
||||
if (position < 0) position = 0
|
||||
var items = []
|
||||
for (var i = Math.max(0, position - radius); i < Math.min(indices.length, position + radius + 1); i++) {
|
||||
items.push({ imageIndex: indices[i], relativeIndex: i - position })
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// Preserve overlapping delegates as selection moves, including their decoded
|
||||
// images. Replacing the entire model on every keypress makes previews flicker.
|
||||
function syncWindow(model, items) {
|
||||
var wanted = {}
|
||||
for (var i = 0; i < items.length; i++) wanted[items[i].imageIndex] = true
|
||||
for (var i = model.count - 1; i >= 0; i--) {
|
||||
if (!wanted[model.get(i).imageIndex]) model.remove(i)
|
||||
}
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var existing = i
|
||||
while (existing < model.count && model.get(existing).imageIndex !== items[i].imageIndex) existing++
|
||||
if (existing === model.count) {
|
||||
model.insert(i, items[i])
|
||||
} else {
|
||||
if (existing !== i) model.move(existing, i, 1)
|
||||
model.setProperty(i, "relativeIndex", items[i].relativeIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = {
|
||||
nameForPath: nameForPath,
|
||||
@@ -92,6 +131,9 @@ if (typeof module !== "undefined") {
|
||||
filteredPosition: filteredPosition,
|
||||
selectedFilteredPosition: selectedFilteredPosition,
|
||||
indexForSelectedImage: indexForSelectedImage,
|
||||
nextSelectedIndexForFilter: nextSelectedIndexForFilter
|
||||
nextSelectedIndexForFilter: nextSelectedIndexForFilter,
|
||||
matchingIndices: matchingIndices,
|
||||
visibleWindow: visibleWindow,
|
||||
syncWindow: syncWindow
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user