Files
qbz/crates/qbz-qt/qml/views/CoverLightbox.qml
T
vicrodh a53e8a7af7 feat(qt): custom covers for playlists + the app-wide custom-art sweep
Two halves of one feature:

- Playlist custom covers (Qt-first; no Slint counterpart yet): the
  playlist header cover gets the cover menu (Add/Change/Remove) and the
  lightbox on left-click. The store is a NEW file,
  custom_playlist_covers.json — a `playlists` key inside the shared
  custom_artwork.json would be silently dropped the next time the Slint
  app wrote an override (its store round-trips only known keys). The
  override beats the playlist's own Qobuz graphic AND the member-cover
  mosaic on the header, the sidebar rows, the cards and the library
  grid.
- Album custom covers now apply everywhere, not just the album header:
  doc builders route an album's art URL through
  cover_artwork_qt::prefer_album_cover (the override's path flows
  through the ordinary artwork pipeline — artwork_qt::classify's
  LocalFile arm resolves it synchronously, no QML change). Swept: album
  page carousels (more-from-artist / suggestions / Last.fm similar),
  artist page releases, search result cards, and the playlist mosaics
  (a member album with a custom cover contributes THAT art to the
  collage tiles).

km
2026-08-11 01:38:18 -06:00

87 lines
2.7 KiB
QML

// Cover lightbox — NEW in the Qt port (no Slint counterpart): the album art
// at its best available quality, fitted to the window. Opened by left-click
// on the AlbumView cover or by the cover menu's "View cover" entry.
//
// Shape follows TrackInfoModal: a full-window Popup parented to
// Overlay.overlay, a dim scrim whose click closes, Escape closes. The image
// asks for a decode capped at the window size (sourceSize), so a mega
// variant does not cost a full-res decode to display at ~90% of the window.
import QtQuick
import QtQuick.Controls
import com.blitzfc.qbz
import "../theme"
Popup {
id: root
parent: Overlay.overlay
x: 0
y: 0
width: parent ? parent.width : 0
height: parent ? parent.height : 0
padding: 0
closePolicy: Popup.CloseOnEscape
// file:// (custom cover) or the remote best() URL. Set through openWith.
property string artSource: ""
function openWith(source) {
// Builders hand over remote URLs, file:// urls, and (for custom
// covers) bare absolute paths — normalize here, once.
if (source.charAt(0) === "/") source = "file://" + source
root.artSource = source
root.open()
}
QbzTheme { id: theme }
background: Rectangle {
color: "#bf000000"
}
contentItem: Item {
// Scrim click closes; clicks on the image itself are swallowed so
// the lightbox stays up while the user inspects it.
MouseArea {
anchors.fill: parent
onClicked: root.close()
}
// Fit the LONGER window axis at 90%, so the cover never touches the
// rim. Covers are square; PreserveAspectFit keeps any odd one honest.
readonly property real side: Math.min(root.width, root.height) * 0.9
Image {
id: art
anchors.centerIn: parent
width: parent.side
height: parent.side
source: root.artSource
asynchronous: true
fillMode: Image.PreserveAspectFit
sourceSize: Qt.size(parent.side, parent.side)
smooth: true
visible: status === Image.Ready
MouseArea {
anchors.fill: parent
onClicked: {} // swallow — the scrim's close must not fire here
}
}
QbzSpinner {
anchors.centerIn: parent
visible: root.artSource !== "" && art.status === Image.Loading
}
Text {
anchors.centerIn: parent
visible: root.artSource !== "" && art.status === Image.Error
text: QbzSession.tr("Could not load the image", QbzSession.trRev)
color: theme.textSecondary
font.pixelSize: theme.fontBody
}
}
}