Handle explicit MPRIS Play/Pause without toggling paused playback, remove the 10,000-favorite cutoff, and sort albums by original release dates. Isolate Library filters by tab, include purchased release dates, and support server-ranked artist popularity with safe pagination. Validated with 766 Qt tests, QML checks, release-sort regressions, and a release build. Refs #750, #751. cx
92 lines
4.0 KiB
QML
92 lines
4.0 KiB
QML
// Vertical A-Z jump strip (favorites/FavoritesView.slint:398 `AlphaStrip`;
|
|
// LocalLibraryView.slint:577 is a 1:1 lift of the same component). 18px
|
|
// gutter, 15px rows, 9px semibold muted letters that go accent on hover.
|
|
// Emits jump(ordinal, index); the host scrolls its own view — exactly like
|
|
// the Slint, which scrolls proportionally on the grouped grids (:1996) and by
|
|
// row index on the uniform track list (:1687).
|
|
//
|
|
// PROMOTED from views/local/AlphaStrip.qml, unchanged except for the import
|
|
// depth — same move QbzMultiSelectBar made and for the same reason: the Slint
|
|
// component was always shared (Local Library albums/artists/tracks AND the
|
|
// Library favorites tabs), so the "local" folder was a naming accident. The
|
|
// old views/local/AlphaStrip.qml is GONE; this file is the only copy.
|
|
// Call sites: views/local/{LocalAlbumsTab,LocalArtistsTab,LocalTracksTab}.qml,
|
|
// views/library/{LibraryAlbumsList}.qml and views/LibraryView.qml.
|
|
|
|
import QtQuick
|
|
import com.blitzfc.qbz
|
|
import "../theme"
|
|
|
|
Item {
|
|
id: root
|
|
|
|
/// [{ letter, index }] — index = the row ordinal in the flat model.
|
|
property var jumps: []
|
|
/// Paint the stable # A-Z affordance even when a query has no row for a
|
|
/// letter. Missing buckets are deliberately inert and dimmed; present
|
|
/// buckets retain their exact native/global row index.
|
|
property bool completeAlphabet: false
|
|
signal jump(int ordinal, int index)
|
|
|
|
function entryAt(position) {
|
|
if (!root.completeAlphabet)
|
|
return (root.jumps || [])[position] || ({ "letter": "", "index": -1 })
|
|
var targets = ({})
|
|
var source = root.jumps || []
|
|
for (var i = 0; i < source.length; i++) {
|
|
var letter = String(source[i].letter || "#").toUpperCase()
|
|
if (letter < "A" || letter > "Z") letter = "#"
|
|
if (targets[letter] === undefined)
|
|
targets[letter] = Number(source[i].index)
|
|
}
|
|
var letter = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(position)
|
|
return { "letter": letter,
|
|
"index": targets[letter] === undefined ? -1 : targets[letter] }
|
|
}
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
width: 18
|
|
|
|
Column {
|
|
anchors.centerIn: parent
|
|
spacing: 0
|
|
Repeater {
|
|
// An integer model makes the full rail structural: it always
|
|
// creates exactly 27 delegates, regardless of when the async
|
|
// jump document lands or how a JS-array binding is invalidated.
|
|
model: root.completeAlphabet ? 27 : (root.jumps || []).length
|
|
delegate: Item {
|
|
id: cell
|
|
required property int index
|
|
readonly property var entry: root.entryAt(index)
|
|
width: root.width
|
|
// A complete rail is 27 entries (# + A-Z). Keep the reference
|
|
// 15px row when it fits, then compress uniformly on short
|
|
// windows instead of painting letters outside the viewport.
|
|
height: Math.max(1, Math.min(15,
|
|
root.height / Math.max(1, root.completeAlphabet
|
|
? 27 : (root.jumps || []).length)))
|
|
Text {
|
|
anchors.fill: parent
|
|
text: cell.entry.letter
|
|
color: cell.entry.index < 0 ? theme.alphaTier(24)
|
|
: cellArea.containsMouse ? theme.accent : theme.textMuted
|
|
font.pixelSize: Math.min(9, Math.max(7, parent.height - 1))
|
|
font.weight: theme.weightSemibold
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
MouseArea {
|
|
id: cellArea
|
|
anchors.fill: parent
|
|
enabled: cell.entry.index >= 0
|
|
hoverEnabled: true
|
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
onClicked: root.jump(cell.index, cell.entry.index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|