Files
qbz/crates/qbz-qt/qml/SectionRail.qml
T
vicrodh 86de977f3c feat(qbz-qt): phase 8 — AlbumView and ArtistView 1:1
AlbumPageView replica: 224px cover, credits, meta, description modal,
action row, quality badge + track search, exact tracklist (playing-row
pill, disc/work headers, live hearts, row menus), label/awards
sidebar, same-artist + suggestions carousels. ArtistPageView replica:
circular portrait, bio modal, From catalog/In library toggle (both
arms live), JUMP TO tabs with jump-scroll, Popular Tracks with Load
more, server-bucketed release sections paged per section, Appears On,
Network sidebar (labels, similar artists). Card clicks navigate
everywhere via the nav stack; new playback paths (play from track,
shuffled album, track lists). Owner data live: album 654ms, artist
670ms loads.

km
2026-07-27 17:49:08 -06:00

128 lines
4.2 KiB
QML

// Horizontal album rail (Carousel.slint) — extracted for reuse by the
// detail views (AlbumView / ArtistView): section header + page chevrons +
// clipped ListView of the shared AlbumCard, with Cider-style edge fades.
import QtQuick
import com.blitzfc.qbz
Column {
id: root
property string title: ""
property var items: []
// The host's url-keyed cover map ({url: file://path}).
property var coverMap: ({})
QbzTheme { id: theme }
width: parent ? parent.width : 0
spacing: 12
readonly property int perPage: Math.max(1, Math.floor((rail.width + 32) / 232))
readonly property int step: perPage * 232
readonly property real maxScroll: Math.max(0, rail.contentWidth - rail.width)
component RailNavBtn: Rectangle {
property string name: ""
property bool btnEnabled: true
signal clicked()
width: 28
height: 28
radius: 14
opacity: btnEnabled ? 1.0 : 0.4
color: (nbArea.containsMouse && btnEnabled) ? theme.surfaceHover : theme.surfaceElevated
QbzIcon {
name: parent.name
width: 15
height: 15
anchors.centerIn: parent
tintName: parent.btnEnabled ? "primary" : "muted"
}
MouseArea {
id: nbArea
anchors.fill: parent
enabled: parent.btnEnabled
hoverEnabled: true
cursorShape: parent.btnEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: parent.clicked()
}
}
Item {
width: parent.width
height: 28
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: root.title
color: theme.textPrimary
font.pixelSize: theme.fontSection
font.weight: theme.weightSemibold
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 4
RailNavBtn {
name: "chevron-left"
btnEnabled: rail.contentX > 1
onClicked: rail.contentX = Math.max(0, rail.contentX - root.step)
}
RailNavBtn {
name: "chevron-right"
btnEnabled: rail.contentX < root.maxScroll - 1
onClicked: rail.contentX = Math.min(root.maxScroll, rail.contentX + root.step)
}
}
}
Item {
width: parent.width
height: 246
ListView {
id: rail
anchors.fill: parent
orientation: ListView.Horizontal
spacing: 32
clip: true
boundsBehavior: Flickable.StopAtBounds
model: root.items
delegate: AlbumCard {
albumId: modelData.id
title: modelData.title
artist: modelData.artist
artistId: modelData.artistId
genre: modelData.genre
year: modelData.year
qualityTier: modelData.qualityTier
artSource: root.coverMap[modelData.artUrl] || ""
isFavorite: false
}
}
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 56
opacity: rail.contentX > 1 ? 1.0 : 0.0
Behavior on opacity { NumberAnimation { duration: 150 } }
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: theme.surfaceMain }
GradientStop { position: 1.0; color: "transparent" }
}
}
Rectangle {
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 56
opacity: rail.contentX < root.maxScroll - 1 ? 1.0 : 0.0
Behavior on opacity { NumberAnimation { duration: 150 } }
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: theme.surfaceMain }
}
}
}
}