fix(library): correct Tracks viewport restoration

Restore the semantic anchor in viewport coordinates and suppress lazy-page requests while the ListView is being repositioned. This avoids treating content coordinates as screen offsets and prevents recursive page loads during restoration.

cx
This commit is contained in:
vicrodh committed 2026-08-23 16:58:16 -06:00
1 parent f83e469fd9
commit 3ed48163aa
2 files changed
+19 -4

No files matched your search

@@ -33,6 +33,9 @@ Item {
// contentY change before that property turns true. All of those changes
// describe the same page miss and must collapse into one bridge call.
property bool pageRequestPending: false
// Programmatic viewport restoration emits the same Flickable signals as
// user input. Keep those signals from recursively requesting another page.
property bool restoringPageAnchor: false
// Invalidates a deferred viewport restore when another document/model
// replacement wins before Qt's next event-loop turn.
property int entriesEpoch: 0
@@ -40,6 +43,7 @@ Item {
function requestNextPage(force) {
if (root.nativeModelActive
|| root.pageRequestPending
|| root.restoringPageAnchor
|| !QbzLocal.localTracksHasMore
|| QbzLocal.localTracksLoadingMore
|| QbzLocal.localTracksLoading)
@@ -88,8 +92,12 @@ Item {
return {
"id": String(root.entries[i].row.id),
// Position inside the viewport, including a partially clipped
// first row or a group header immediately above it.
"screenY": cell ? cell.mapToItem(list, 0, 0).y : 0
// first row or a group header immediately above it. A ListView
// delegate's y is in contentItem coordinates, so subtracting the
// viewport's contentY is the visual coordinate. mapToItem(list)
// preserves the content coordinate here and would add thousands
// of rows back into the restore calculation.
"screenY": cell ? cell.y - list.contentY : 0
}
}
@@ -115,15 +123,17 @@ Item {
Qt.callLater(function () {
if (epoch !== root.entriesEpoch || root.nativeModelActive)
return
root.restoringPageAnchor = true
list.forceLayout()
list.positionViewAtIndex(target, ListView.Beginning)
list.forceLayout()
var cell = list.itemAtIndex(target)
var currentScreenY = cell ? cell.mapToItem(list, 0, 0).y : 0
var currentScreenY = cell ? cell.y - list.contentY : 0
var wanted = list.contentY + currentScreenY - anchor.screenY
var minY = list.originY
var maxY = minY + Math.max(0, list.contentHeight - list.height)
list.contentY = Math.max(minY, Math.min(wanted, maxY))
root.restoringPageAnchor = false
root.reportSoon()
})
}
+6 -1
View File
@@ -528,7 +528,12 @@ mod phase_a_tests {
let qml = include_str!("../qml/views/local/LocalTracksTab.qml");
assert!(qml.contains("function capturePageAnchor()"));
assert!(qml.contains("String(root.entries[i].row.id)"));
assert!(qml.contains("cell.mapToItem(list, 0, 0).y"));
assert!(qml.contains("cell.y - list.contentY"));
assert!(!qml.contains("cell.mapToItem(list"));
assert!(qml.contains("|| root.restoringPageAnchor"));
assert!(qml.contains(
"var wanted = list.contentY + currentScreenY - anchor.screenY"
));
assert!(qml.contains("function restorePageAnchor(anchor, nextEntries, epoch)"));
assert!(qml.contains("Qt.callLater(function ()"));
assert!(qml.contains("list.positionViewAtIndex(target, ListView.Beginning)"));