From baed7bb7855ee6e83dde3f9197ab94303cb377c2 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Fri, 11 Sep 2026 09:45:47 -0600 Subject: [PATCH] fix(cards): carry the Qobuz album version in card titles + tooltip when elided Every album card producer copied `title` raw although the model carries `version` ("50th Anniversary", "Deluxe"); the album page, info modal and quick view already used `format_album_title`. The Artist page listed two identical "All Things Must Pass" cards for two different editions. - Route the 8 card producers through `album_qt::format_album_title` (artist page buckets, album carousels, label grid, Home rails, Library ALL feed, Artist Scene); search_qt drops its private duplicate of the helper. - AlbumCard.qml: the title is always the full string, elided at the card width; when Qt reports `truncated`, hovering shows the whole title in a Controls ToolTip (QualityBadge pattern, no character counting, no ticker). Verified: qmllint clean, qbz-qt suite 782/782, release build. cc --- crates/qbz-qt/qml/cards/AlbumCard.qml | 43 +++++++++++++++++++++++++++ crates/qbz-qt/src/album_qt.rs | 4 +-- crates/qbz-qt/src/artist_qt.rs | 5 +++- crates/qbz-qt/src/artist_scene_qt.rs | 2 +- crates/qbz-qt/src/home_qt.rs | 6 ++-- crates/qbz-qt/src/library_qt.rs | 2 +- crates/qbz-qt/src/search_qt.rs | 7 +---- 7 files changed, 55 insertions(+), 14 deletions(-) diff --git a/crates/qbz-qt/qml/cards/AlbumCard.qml b/crates/qbz-qt/qml/cards/AlbumCard.qml index cfd2af5ac..65480fc4a 100644 --- a/crates/qbz-qt/qml/cards/AlbumCard.qml +++ b/crates/qbz-qt/qml/cards/AlbumCard.qml @@ -921,6 +921,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter spacing: 2 Text { + id: titleText width: parent.width height: 20 text: root.title @@ -930,6 +931,48 @@ Rectangle { font.weight: theme.weightMedium verticalAlignment: Text.AlignVCenter elide: Text.ElideRight + // The title is ALWAYS the full one (version included) and + // elides at the card's width; when it is cut, hovering shows + // the whole text in a small bubble above the line. `truncated` + // is Qt's own verdict on the elision, so a title that fits + // never raises a tooltip — no character counting. Qt's own + // ToolTip, like QualityBadge.qml: a recycled delegate deep + // inside a view cannot name the shell's tooltip overlay. + ToolTip { + id: titleTip + visible: titleArea.containsMouse && titleText.truncated + && root.title !== "" + text: root.title + delay: 350 + timeout: -1 + padding: 0 + x: 0 + y: -height - 4 + contentWidth: titleTipText.implicitWidth + contentHeight: titleTipText.implicitHeight + contentItem: Text { + id: titleTipText + text: titleTip.text + color: theme.textPrimary + font.pixelSize: 11 + font.weight: theme.weightMedium + wrapMode: Text.Wrap + width: Math.min(implicitWidth, 300) + maximumLineCount: 3 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + leftPadding: 9 + rightPadding: 9 + topPadding: 5 + bottomPadding: 5 + } + background: Rectangle { + color: theme.surfaceElevated + radius: theme.radiusSm + border.width: 1 + border.color: theme.borderSubtle + } + } MouseArea { id: titleArea anchors.fill: parent diff --git a/crates/qbz-qt/src/album_qt.rs b/crates/qbz-qt/src/album_qt.rs index 959d43c42..679e6d01c 100644 --- a/crates/qbz-qt/src/album_qt.rs +++ b/crates/qbz-qt/src/album_qt.rs @@ -1086,7 +1086,7 @@ fn map_release_card(release: &qbz_models::PageArtistRelease) -> AlbumCardData { // Heart from the favourite-id cache (see `AlbumCardData::is_favorite`). is_favorite: crate::fav_cache_qt::is_album_favorite(&release.id), id: release.id.clone(), - title: release.title.clone(), + title: format_album_title(&release.title, release.version.as_deref()), artist, artist_id, genre: release @@ -1213,7 +1213,7 @@ pub async fn load_suggestions( is_pinned: crate::sidebar_qt::is_pinned("album", &a.id), is_favorite: crate::fav_cache_qt::is_album_favorite(&a.id), id: a.id.clone(), - title: a.title.clone(), + title: format_album_title(&a.title, a.version.as_deref()), artist, artist_id: card_artist_id, genre: a.genre.as_ref().map(|g| g.name.clone()).unwrap_or_default(), diff --git a/crates/qbz-qt/src/artist_qt.rs b/crates/qbz-qt/src/artist_qt.rs index 088c2947d..4bbf08ca4 100644 --- a/crates/qbz-qt/src/artist_qt.rs +++ b/crates/qbz-qt/src/artist_qt.rs @@ -442,7 +442,10 @@ pub(crate) fn map_release(release: &PageArtistRelease) -> AlbumCardData { // mounts the same AlbumCard as Home (see `AlbumCardData::is_favorite`). is_favorite: crate::fav_cache_qt::is_album_favorite(&release.id), id: release.id.clone(), - title: release.title.clone(), + // The Qobuz `version` ("50th Anniversary", "Deluxe") rides the title + // everywhere else in the app (`format_album_title`); the card is not + // the exception — a title without it points at the wrong edition. + title: crate::album_qt::format_album_title(&release.title, release.version.as_deref()), artist, artist_id, genre: release diff --git a/crates/qbz-qt/src/artist_scene_qt.rs b/crates/qbz-qt/src/artist_scene_qt.rs index d8024260e..dca0042eb 100644 --- a/crates/qbz-qt/src/artist_scene_qt.rs +++ b/crates/qbz-qt/src/artist_scene_qt.rs @@ -1115,7 +1115,7 @@ fn map_album(album: &Album) -> SceneAlbum { .unwrap_or_default(); SceneAlbum { album_id: album.id.clone(), - title: album.title.clone(), + title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()), artist: album.artist.name.clone(), artist_id: album.artist.id.to_string(), // `image.small || image.large` — Tauri's exact order diff --git a/crates/qbz-qt/src/home_qt.rs b/crates/qbz-qt/src/home_qt.rs index 8eda9e3e5..9c727612e 100644 --- a/crates/qbz-qt/src/home_qt.rs +++ b/crates/qbz-qt/src/home_qt.rs @@ -1797,7 +1797,7 @@ pub(crate) fn map_album(album: DiscoverAlbum) -> HomeCard { is_pinned, is_favorite, id: album.id, - title: album.title, + title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()), artist, artist_id, blacklist_artist_ids, @@ -1893,7 +1893,7 @@ fn map_slim(index: usize, album: DiscoverAlbum) -> HomeCard { // false. A future SlimCard with a heart inherits a correct row. is_favorite: crate::fav_cache_qt::is_album_favorite(&album.id), id: album.id, - title: album.title, + title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()), artist: subtitle, artist_id, blacklist_artist_ids, @@ -2101,7 +2101,7 @@ pub(crate) fn map_flat_album(album: Album) -> HomeCard { // DiscoverBrowse, so one stamp covers four surfaces. is_favorite: crate::fav_cache_qt::is_album_favorite(&album.id), id: album.id, - title: album.title, + title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()), artist: album.artist.name, artist_id: album.artist.id.to_string(), blacklist_artist_ids, diff --git a/crates/qbz-qt/src/library_qt.rs b/crates/qbz-qt/src/library_qt.rs index 04a9a4092..ea223cc56 100644 --- a/crates/qbz-qt/src/library_qt.rs +++ b/crates/qbz-qt/src/library_qt.rs @@ -651,7 +651,7 @@ fn map_album(album: Album, ready_offline_tracks: usize) -> FeedItem { group: "favorites".into(), source: "qobuz".into(), id: album.id, - title: album.title, + title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()), subtitle: artist.clone(), artist, artist_id: album.artist.id.to_string(), diff --git a/crates/qbz-qt/src/search_qt.rs b/crates/qbz-qt/src/search_qt.rs index 50f36c389..c8455a544 100644 --- a/crates/qbz-qt/src/search_qt.rs +++ b/crates/qbz-qt/src/search_qt.rs @@ -45,6 +45,7 @@ use std::sync::{Arc, Mutex}; use cxx_qt_lib::QString; pub(crate) use qbz_app::settings::search_service::InteractionAction; +use crate::album_qt::format_album_title; use qbz_app::settings::search_service::SearchService; use qbz_app::shell::AppRuntime; use qbz_core::LoggingAdapter; @@ -320,12 +321,6 @@ fn year_of(date: Option<&str>) -> String { date.and_then(|d| d.get(0..4)).unwrap_or("").to_string() } -fn format_album_title(title: &str, version: Option<&str>) -> String { - match version.map(str::trim).filter(|v| !v.is_empty()) { - Some(v) => format!("{title} ({v})"), - None => title.to_string(), - } -} fn map_album(album: &Album) -> CardRow { CardRow {