feat: artist sections, fav indicator, art scaling fix, volume popup fix

- Artist profile: collapsible Albums / EPs & Singles / Other sections
  keyed on release_type; fetches up to 200 albums per artist
- Favorites: starred icon on favorited tracks, context menu shows
  Add or Remove (not both); IDs cached when fav tracks are loaded
- Shuffle button: one-time shuffle via shuffleNow() without touching
  global shuffle flag, so double-click still plays in order
- Now-playing art: replaced setFixedHeight hack with ArtWidget that
  overrides hasHeightForWidth() — scales smoothly up and down, no min-size
- Volume popup: replaced QMenu (laggy, broken drag) with Qt::Popup QFrame;
  appears below button; fixed size locked at 100% label width

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-24 17:56:47 +01:00
parent 75429faffe
commit 56473cae6f
15 changed files with 370 additions and 109 deletions

View File

@@ -77,6 +77,36 @@ void TrackListModel::removeTrack(int row)
endRemoveRows();
}
void TrackListModel::setFavIds(const QSet<qint64> &ids)
{
m_favIds = ids;
if (!m_tracks.isEmpty())
emit dataChanged(index(0, ColTitle), index(rowCount() - 1, ColTitle),
{Qt::DecorationRole});
}
void TrackListModel::addFavId(qint64 id)
{
m_favIds.insert(id);
for (int r = 0; r < m_tracks.size(); ++r) {
if (m_tracks[r].id == id) {
const auto idx = index(r, ColTitle);
emit dataChanged(idx, idx, {Qt::DecorationRole});
}
}
}
void TrackListModel::removeFavId(qint64 id)
{
m_favIds.remove(id);
for (int r = 0; r < m_tracks.size(); ++r) {
if (m_tracks[r].id == id) {
const auto idx = index(r, ColTitle);
emit dataChanged(idx, idx, {Qt::DecorationRole});
}
}
}
void TrackListModel::setPlayingId(qint64 id)
{
m_playingId = id;
@@ -128,6 +158,10 @@ QVariant TrackListModel::data(const QModelIndex &index, int role) const
return QIcon(QStringLiteral(":/res/icons/media-track-show-active.svg"));
}
if (role == Qt::DecorationRole && index.column() == ColTitle && m_favIds.contains(t.id)) {
return QIcon(QStringLiteral(":/res/icons/starred-symbolic.svg"));
}
if (role == TrackIdRole) return t.id;
if (role == TrackJsonRole) return t.raw;
if (role == HiResRole) return t.hiRes;