feat(qt): queue rows slide into place instead of the list being rebuilt
The Listen list and the sidebar queue now reach their views through QbzKeyedModel. In the Listen list a removed track fades out while the rows below slide up, "play next" slides the queue open, a history row trimmed above the window leaves the rows on screen where they are, and a committed drag lands the row where it was dropped while its neighbours slide (the chevrons keep the animated swap). Identity is the track (source + id, repeats by occurrence); phase, section and phaseIndex are data, so a track ending updates rows in place and moves nothing. The follow-playing position and the cover window now run after the model holds the new rows. A search is a different list and is replaced at once. The sidebar's Up Next and Recently played are Repeaters in Columns, which have no remove transition: a leaving row disappears and the rows below slide up, a joining row fades in, and a page or search change is laid out synchronously without animation. Drop slots count the Repeater's own items. tst_keyed_model gains the Repeater-in-a-Column case (silent first page and page change, sliding rows that keep their items, fading insert). cc
This commit is contained in:
3 files changed
+168
-17
No files matched your search
@@ -38,6 +38,24 @@ Rectangle {
|
||||
readonly property var currentRow: doc.current || null
|
||||
readonly property var upcoming: doc.upcoming || []
|
||||
readonly property var historyRows: doc.history || []
|
||||
// Keyed models for both lists (2026-09-14): a track leaving Up Next (it
|
||||
// started playing, it was removed) disappears and the rows below slide up;
|
||||
// a track joining slides the list open; nothing is rebuilt. The rows are
|
||||
// Repeaters in a Column, so the motion is the Column's own add / move
|
||||
// transitions — a positioner has no remove transition. Identity is the
|
||||
// track (source + id, repeats by occurrence); another page or search
|
||||
// replaces the list at once.
|
||||
QbzKeyedModel {
|
||||
id: upcomingModel
|
||||
rows: root.upcoming
|
||||
keyOf: function (row) { return (row.source || "") + ":" + row.id }
|
||||
scope: JSON.stringify([root.doc.searchQuery || "", root.doc.page || 0])
|
||||
}
|
||||
QbzKeyedModel {
|
||||
id: historyModel
|
||||
rows: root.historyRows
|
||||
keyOf: function (row) { return (row.source || "") + ":" + row.id }
|
||||
}
|
||||
readonly property var settingsDoc: {
|
||||
try { return JSON.parse(QbzBridge.settingsJson) }
|
||||
catch (e) { return ({}) }
|
||||
@@ -138,7 +156,8 @@ Rectangle {
|
||||
// arithmetic model to keep in sync. Top half of row k -> slot k, bottom
|
||||
// half -> slot k+1, past the last row -> N (append). <=40 rows per page.
|
||||
function slotFromPointer(winY) {
|
||||
var n = root.upcoming.length
|
||||
// The Repeater's own count: its items are what the pointer is over.
|
||||
var n = upcomingRepeater.count
|
||||
for (var i = 0; i < n; i++) {
|
||||
var d = upcomingRepeater.itemAt(i)
|
||||
if (d && winY < d.rowCenterY())
|
||||
@@ -223,7 +242,13 @@ Rectangle {
|
||||
root.coverMap = Object.assign({}, m)
|
||||
}
|
||||
}
|
||||
Component.onCompleted: dispatchCovers()
|
||||
Component.onCompleted: {
|
||||
// The lists' Columns, laid out synchronously around a reset so a new
|
||||
// page or search appears at once (QbzKeyedModel).
|
||||
upcomingModel.views = [upNextSection]
|
||||
historyModel.views = [historyBody]
|
||||
dispatchCovers()
|
||||
}
|
||||
onDocChanged: dispatchCovers()
|
||||
onQueueTrackArtworkChanged: dispatchCovers()
|
||||
// Collect the covers of the document CURRENTLY on the property and ask the
|
||||
@@ -1026,13 +1051,18 @@ Rectangle {
|
||||
font.letterSpacing: 0.5
|
||||
}
|
||||
|
||||
add: QbzRowAdd { enabled: upcomingModel.animate }
|
||||
move: QbzRowDisplaced { enabled: upcomingModel.animate }
|
||||
|
||||
Repeater {
|
||||
id: upcomingRepeater
|
||||
model: root.upcoming
|
||||
model: upcomingModel
|
||||
delegate: Column {
|
||||
id: upDelegate
|
||||
required property var modelData
|
||||
required property string rowKey
|
||||
required property int rowRev
|
||||
required property int index
|
||||
readonly property var modelData: upcomingModel.row(rowKey, rowRev)
|
||||
width: parent ? parent.width : 0
|
||||
// Row center in window space — slotFromPointer()
|
||||
// reads it off the Repeater's items.
|
||||
@@ -1167,9 +1197,16 @@ Rectangle {
|
||||
font.weight: theme.weightSemibold
|
||||
font.letterSpacing: 0.5
|
||||
}
|
||||
add: QbzRowAdd { enabled: historyModel.animate }
|
||||
move: QbzRowDisplaced { enabled: historyModel.animate }
|
||||
|
||||
Repeater {
|
||||
model: root.historyRows
|
||||
model: historyModel
|
||||
delegate: QueueRow {
|
||||
required property string rowKey
|
||||
required property int rowRev
|
||||
required property int index
|
||||
readonly property var modelData: historyModel.row(rowKey, rowRev)
|
||||
width: parent.width
|
||||
row: modelData
|
||||
rowIndex: index
|
||||
|
||||
Reference in new issue
Block a user