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

@@ -4,10 +4,9 @@
#include "../util/icon.hpp"
#include <QToolButton>
#include <QWidgetAction>
#include <QMenu>
#include <QLabel>
#include <QFrame>
#include <QVBoxLayout>
#include <QLabel>
/// A toolbar button that shows a volume slider popup when clicked.
class VolumeButton : public QToolButton
@@ -17,33 +16,36 @@ class VolumeButton : public QToolButton
public:
explicit VolumeButton(QWidget *parent = nullptr) : QToolButton(parent)
{
setPopupMode(QToolButton::InstantPopup);
setIcon(Icon::volumeHigh());
auto *menu = new QMenu(this);
auto *widget = new QWidget(menu);
widget->setMinimumWidth(72);
auto *layout = new QVBoxLayout(widget);
layout->setContentsMargins(6, 6, 6, 6);
// Qt::Popup closes automatically when the user clicks outside.
m_popup = new QFrame(this, Qt::Popup);
m_popup->setFrameShape(QFrame::StyledPanel);
m_popup->setFrameShadow(QFrame::Raised);
m_label = new QLabel("80%", widget);
auto *layout = new QVBoxLayout(m_popup);
layout->setContentsMargins(10, 10, 10, 10);
layout->setSpacing(6);
m_label = new QLabel(QStringLiteral("80%"), m_popup);
m_label->setAlignment(Qt::AlignCenter);
layout->addWidget(m_label);
m_slider = new ClickableSlider(Qt::Vertical, widget);
m_slider = new ClickableSlider(Qt::Vertical, m_popup);
m_slider->setRange(0, 100);
m_slider->setValue(80);
m_slider->setFixedHeight(120);
layout->addWidget(m_slider, 0, Qt::AlignHCenter);
layout->addWidget(m_label);
layout->addWidget(m_slider);
auto *action = new QWidgetAction(menu);
action->setDefaultWidget(widget);
menu->addAction(action);
setMenu(menu);
// Size the popup at its maximum (label = "100%") and lock it
m_label->setText(QStringLiteral("100%"));
m_popup->adjustSize();
m_popup->setFixedSize(m_popup->sizeHint());
m_label->setText(QStringLiteral("80%"));
connect(this, &QToolButton::clicked, this, &VolumeButton::togglePopup);
connect(m_slider, &QSlider::valueChanged, this, [this](int v) {
m_label->setText(QString::number(v) + "%");
m_label->setText(QString::number(v) + QStringLiteral("%"));
updateIcon(v);
emit volumeChanged(v);
});
@@ -56,14 +58,31 @@ public:
m_slider->blockSignals(true);
m_slider->setValue(v);
m_slider->blockSignals(false);
m_label->setText(QString::number(v) + "%");
m_label->setText(QString::number(v) + QStringLiteral("%"));
updateIcon(v);
}
signals:
void volumeChanged(int volume);
private slots:
void togglePopup()
{
if (m_popup->isVisible()) {
m_popup->hide();
return;
}
// Centre popup horizontally over button, place below it
const QPoint global = mapToGlobal(
QPoint(width() / 2 - m_popup->width() / 2,
height() + 4));
m_popup->move(global);
m_popup->show();
m_popup->raise();
}
private:
QFrame *m_popup = nullptr;
ClickableSlider *m_slider = nullptr;
QLabel *m_label = nullptr;