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 // contentY change before that property turns true. All of those changes
// describe the same page miss and must collapse into one bridge call. // describe the same page miss and must collapse into one bridge call.
property bool pageRequestPending: false 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 // Invalidates a deferred viewport restore when another document/model
// replacement wins before Qt's next event-loop turn. // replacement wins before Qt's next event-loop turn.
property int entriesEpoch: 0 property int entriesEpoch: 0
@@ -40,6 +43,7 @@ Item {
function requestNextPage(force) { function requestNextPage(force) {
if (root.nativeModelActive if (root.nativeModelActive
|| root.pageRequestPending || root.pageRequestPending
|| root.restoringPageAnchor
|| !QbzLocal.localTracksHasMore || !QbzLocal.localTracksHasMore
|| QbzLocal.localTracksLoadingMore || QbzLocal.localTracksLoadingMore
|| QbzLocal.localTracksLoading) || QbzLocal.localTracksLoading)
@@ -88,8 +92,12 @@ Item {
return { return {
"id": String(root.entries[i].row.id), "id": String(root.entries[i].row.id),
// Position inside the viewport, including a partially clipped // Position inside the viewport, including a partially clipped
// first row or a group header immediately above it. // first row or a group header immediately above it. A ListView
"screenY": cell ? cell.mapToItem(list, 0, 0).y : 0 // 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 () { Qt.callLater(function () {
if (epoch !== root.entriesEpoch || root.nativeModelActive) if (epoch !== root.entriesEpoch || root.nativeModelActive)
return return
root.restoringPageAnchor = true
list.forceLayout() list.forceLayout()
list.positionViewAtIndex(target, ListView.Beginning) list.positionViewAtIndex(target, ListView.Beginning)
list.forceLayout() list.forceLayout()
var cell = list.itemAtIndex(target) 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 wanted = list.contentY + currentScreenY - anchor.screenY
var minY = list.originY var minY = list.originY
var maxY = minY + Math.max(0, list.contentHeight - list.height) var maxY = minY + Math.max(0, list.contentHeight - list.height)
list.contentY = Math.max(minY, Math.min(wanted, maxY)) list.contentY = Math.max(minY, Math.min(wanted, maxY))
root.restoringPageAnchor = false
root.reportSoon() root.reportSoon()
}) })
} }
+6 -1
View File
@@ -528,7 +528,12 @@ mod phase_a_tests {
let qml = include_str!("../qml/views/local/LocalTracksTab.qml"); let qml = include_str!("../qml/views/local/LocalTracksTab.qml");
assert!(qml.contains("function capturePageAnchor()")); assert!(qml.contains("function capturePageAnchor()"));
assert!(qml.contains("String(root.entries[i].row.id)")); 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("function restorePageAnchor(anchor, nextEntries, epoch)"));
assert!(qml.contains("Qt.callLater(function ()")); assert!(qml.contains("Qt.callLater(function ()"));
assert!(qml.contains("list.positionViewAtIndex(target, ListView.Beginning)")); assert!(qml.contains("list.positionViewAtIndex(target, ListView.Beginning)"));