refactor: rewrite toolbar to match spotify-qt structure exactly

Flat QToolBar with sequential addAction/addWidget — no nested containers.
Order: [art][track] | [prev][play][next] [leftSpacer] [progress][time] [rightSpacer] [shuffle][vol][queue][search]

Centering via resizeEvent: both spacers get width/6 of total bar width,
mirroring the exact approach used in spotify-qt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-24 10:28:10 +01:00
parent 8950fd2914
commit 15ba6f7a1e
2 changed files with 90 additions and 127 deletions

View File

@@ -2,11 +2,8 @@
#include "../util/settings.hpp" #include "../util/settings.hpp"
#include "../model/tracklistmodel.hpp" #include "../model/tracklistmodel.hpp"
#include <QWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QToolButton>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QResizeEvent>
MainToolBar::MainToolBar(QobuzBackend *backend, PlayQueue *queue, QWidget *parent) MainToolBar::MainToolBar(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
: QToolBar(parent) : QToolBar(parent)
@@ -21,123 +18,79 @@ MainToolBar::MainToolBar(QobuzBackend *backend, PlayQueue *queue, QWidget *paren
m_nam = new QNetworkAccessManager(this); m_nam = new QNetworkAccessManager(this);
connect(m_nam, &QNetworkAccessManager::finished, this, &MainToolBar::onAlbumArtReady); connect(m_nam, &QNetworkAccessManager::finished, this, &MainToolBar::onAlbumArtReady);
// ---------------------------------------------------------------- // ---- Album art ----
// Root container — three equal-stretch columns m_artLabel = new QLabel(this);
// ---------------------------------------------------------------- m_artLabel->setFixedSize(36, 36);
auto *root = new QWidget(this);
auto *rootLo = new QHBoxLayout(root);
rootLo->setContentsMargins(6, 2, 6, 2);
rootLo->setSpacing(0);
// ---- LEFT: album art | track info | prev/play/next ----
auto *leftWidget = new QWidget(root);
auto *leftLo = new QHBoxLayout(leftWidget);
leftLo->setContentsMargins(0, 0, 0, 0);
leftLo->setSpacing(8);
m_artLabel = new QLabel(leftWidget);
m_artLabel->setFixedSize(44, 44);
m_artLabel->setScaledContents(true); m_artLabel->setScaledContents(true);
m_artLabel->setStyleSheet("border: 1px solid #444; background: #1a1a1a; border-radius: 3px;"); m_artLabel->setStyleSheet("border: 1px solid #444; background: #1a1a1a; border-radius: 3px;");
m_artLabel->setPixmap(QIcon(":/res/icons/view-media-album-cover.svg").pixmap(40, 40)); m_artLabel->setPixmap(QIcon(":/res/icons/view-media-album-cover.svg").pixmap(32, 32));
addWidget(m_artLabel);
m_trackLabel = new QLabel(tr("Not playing"), leftWidget); // ---- Track label ----
m_trackLabel->setMinimumWidth(100); m_trackLabel = new QLabel(tr("Not playing"), this);
m_trackLabel->setMaximumWidth(220); m_trackLabel->setMinimumWidth(80);
m_trackLabel->setMaximumWidth(200);
m_trackLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); m_trackLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
m_trackLabel->setWordWrap(false); addWidget(m_trackLabel);
auto makeBtn = [&](const QIcon &icon, const QString &tip) -> QToolButton * { addSeparator();
auto *btn = new QToolButton(leftWidget);
btn->setIcon(icon);
btn->setToolTip(tip);
btn->setAutoRaise(true);
btn->setIconSize(QSize(22, 22));
return btn;
};
m_prevBtn = makeBtn(Icon::previous(), tr("Previous")); // ---- Media controls ----
m_playBtn = makeBtn(Icon::play(), tr("Play")); m_previous = addAction(Icon::previous(), tr("Previous"));
m_playBtn->setIconSize(QSize(28, 28)); connect(m_previous, &QAction::triggered, this, &MainToolBar::onPrevious);
m_nextBtn = makeBtn(Icon::next(), tr("Next"));
connect(m_prevBtn, &QToolButton::clicked, this, &MainToolBar::onPrevious); m_playPause = addAction(Icon::play(), tr("Play"));
connect(m_playBtn, &QToolButton::clicked, this, &MainToolBar::onPlayPause); connect(m_playPause, &QAction::triggered, this, &MainToolBar::onPlayPause);
connect(m_nextBtn, &QToolButton::clicked, this, &MainToolBar::onNext);
leftLo->addWidget(m_artLabel); m_next = addAction(Icon::next(), tr("Next"));
leftLo->addWidget(m_trackLabel); connect(m_next, &QAction::triggered, this, &MainToolBar::onNext);
leftLo->addWidget(m_prevBtn);
leftLo->addWidget(m_playBtn);
leftLo->addWidget(m_nextBtn);
// ---- CENTER: elapsed | progress slider | total ---- // ---- Left spacer (pushes progress toward center) ----
auto *centerWidget = new QWidget(root); m_leftSpacer = new QWidget(this);
auto *centerLo = new QHBoxLayout(centerWidget); m_leftSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
centerLo->setContentsMargins(0, 0, 0, 0); addWidget(m_leftSpacer);
centerLo->setSpacing(6);
m_elapsedLabel = new QLabel(QStringLiteral("0:00"), centerWidget); // ---- Progress slider ----
m_elapsedLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_progress = new ClickableSlider(Qt::Horizontal, this);
m_elapsedLabel->setMinimumWidth(36);
m_progress = new ClickableSlider(Qt::Horizontal, centerWidget);
m_progress->setRange(0, 1000); m_progress->setRange(0, 1000);
m_progress->setValue(0); m_progress->setValue(0);
m_progress->setMinimumWidth(160); m_progress->setMinimumWidth(200);
m_progress->setMaximumWidth(500);
m_totalLabel = new QLabel(QStringLiteral("0:00"), centerWidget); addWidget(m_progress);
m_totalLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_totalLabel->setMinimumWidth(36);
connect(m_progress, &QSlider::sliderPressed, this, [this] { m_seeking = true; }); connect(m_progress, &QSlider::sliderPressed, this, [this] { m_seeking = true; });
connect(m_progress, &QSlider::sliderReleased, this, &MainToolBar::onProgressReleased); connect(m_progress, &QSlider::sliderReleased, this, &MainToolBar::onProgressReleased);
centerLo->addWidget(m_elapsedLabel); // ---- Position label ----
centerLo->addWidget(m_progress, 1); m_position = new QLabel(QStringLiteral("0:00 / 0:00"), this);
centerLo->addWidget(m_totalLabel); m_position->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
m_position->setMinimumWidth(80);
addWidget(m_position);
// ---- RIGHT: volume + shuffle + queue + search ---- // ---- Right spacer (mirrors left spacer) ----
auto *rightWidget = new QWidget(root); m_rightSpacer = new QWidget(this);
auto *rightLo = new QHBoxLayout(rightWidget); m_rightSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
rightLo->setContentsMargins(0, 0, 0, 0); addWidget(m_rightSpacer);
rightLo->setSpacing(4);
m_volume = new VolumeButton(rightWidget); // ---- Shuffle ----
m_shuffle = addAction(Icon::get(QStringLiteral("media-playlist-shuffle")), tr("Shuffle"));
m_shuffle->setCheckable(true);
connect(m_shuffle, &QAction::toggled, this, &MainToolBar::onShuffleToggled);
// ---- Volume ----
m_volume = new VolumeButton(this);
m_volume->setValue(AppSettings::instance().volume()); m_volume->setValue(AppSettings::instance().volume());
addWidget(m_volume);
connect(m_volume, &VolumeButton::volumeChanged, this, &MainToolBar::onVolumeChanged); connect(m_volume, &VolumeButton::volumeChanged, this, &MainToolBar::onVolumeChanged);
auto makeToggle = [&](const QIcon &icon, const QString &tip) -> QToolButton * { // ---- Queue toggle ----
auto *btn = new QToolButton(rightWidget); m_queueBtn = addAction(Icon::queue(), tr("Queue"));
btn->setIcon(icon); m_queueBtn->setCheckable(true);
btn->setToolTip(tip); connect(m_queueBtn, &QAction::toggled, this, &MainToolBar::queueToggled);
btn->setCheckable(true);
btn->setAutoRaise(true);
btn->setIconSize(QSize(22, 22));
return btn;
};
m_shuffleBtn = makeToggle(Icon::get(QStringLiteral("media-playlist-shuffle")), tr("Shuffle")); // ---- Search toggle ----
m_queueBtn = makeToggle(Icon::queue(), tr("Queue")); m_search = addAction(Icon::search(), tr("Search"));
m_searchBtn = makeToggle(Icon::search(), tr("Search")); m_search->setCheckable(true);
connect(m_search, &QAction::toggled, this, &MainToolBar::searchToggled);
connect(m_shuffleBtn, &QToolButton::toggled, this, &MainToolBar::onShuffleToggled);
connect(m_queueBtn, &QToolButton::toggled, this, &MainToolBar::queueToggled);
connect(m_searchBtn, &QToolButton::toggled, this, &MainToolBar::searchToggled);
rightLo->addStretch(1);
rightLo->addWidget(m_volume);
rightLo->addWidget(m_shuffleBtn);
rightLo->addWidget(m_queueBtn);
rightLo->addWidget(m_searchBtn);
// ---- Assemble root: equal stretch keeps center bar centred ----
rootLo->addWidget(leftWidget, 1);
rootLo->addWidget(centerWidget, 2);
rootLo->addWidget(rightWidget, 1);
root->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
addWidget(root);
// ---- Backend signals ---- // ---- Backend signals ----
connect(m_backend, &QobuzBackend::stateChanged, this, &MainToolBar::onBackendStateChanged); connect(m_backend, &QobuzBackend::stateChanged, this, &MainToolBar::onBackendStateChanged);
@@ -150,13 +103,23 @@ MainToolBar::MainToolBar(QobuzBackend *backend, PlayQueue *queue, QWidget *paren
onQueueChanged(); onQueueChanged();
} }
// ---- resize: keep spacers equal so progress stays centred ----
void MainToolBar::resizeEvent(QResizeEvent *event)
{
QToolBar::resizeEvent(event);
const int spacerWidth = event->size().width() / 6;
m_leftSpacer->setMinimumWidth(spacerWidth);
m_rightSpacer->setMinimumWidth(spacerWidth);
}
// ---- public ---- // ---- public ----
void MainToolBar::setPlaying(bool playing) void MainToolBar::setPlaying(bool playing)
{ {
m_playing = playing; m_playing = playing;
m_playBtn->setIcon(playing ? Icon::pause() : Icon::play()); m_playPause->setIcon(playing ? Icon::pause() : Icon::play());
m_playBtn->setToolTip(playing ? tr("Pause") : tr("Play")); m_playPause->setText(playing ? tr("Pause") : tr("Play"));
} }
void MainToolBar::setCurrentTrack(const QJsonObject &track) void MainToolBar::setCurrentTrack(const QJsonObject &track)
@@ -171,7 +134,7 @@ void MainToolBar::setCurrentTrack(const QJsonObject &track)
} else if (artist.isEmpty()) { } else if (artist.isEmpty()) {
m_trackLabel->setText(title); m_trackLabel->setText(title);
} else { } else {
m_trackLabel->setText(QStringLiteral("%1\n%2").arg(title, artist)); m_trackLabel->setText(QStringLiteral("%1%2").arg(title, artist));
} }
const QString artUrl = track["album"].toObject()["image"].toObject()["small"].toString(); const QString artUrl = track["album"].toObject()["image"].toObject()["small"].toString();
@@ -189,8 +152,10 @@ void MainToolBar::updateProgress(quint64 position, quint64 duration)
m_progress->blockSignals(true); m_progress->blockSignals(true);
m_progress->setValue(sliderPos); m_progress->setValue(sliderPos);
m_progress->blockSignals(false); m_progress->blockSignals(false);
m_elapsedLabel->setText(TrackListModel::formatDuration(static_cast<qint64>(position))); m_position->setText(
m_totalLabel->setText(TrackListModel::formatDuration(static_cast<qint64>(duration))); QStringLiteral("%1 / %2")
.arg(TrackListModel::formatDuration(static_cast<qint64>(position)),
TrackListModel::formatDuration(static_cast<qint64>(duration))));
} }
// ---- private slots ---- // ---- private slots ----
@@ -257,15 +222,14 @@ void MainToolBar::onTrackFinished()
} else { } else {
setPlaying(false); setPlaying(false);
m_progress->setValue(0); m_progress->setValue(0);
m_elapsedLabel->setText(QStringLiteral("0:00")); m_position->setText(QStringLiteral("0:00 / 0:00"));
m_totalLabel->setText(QStringLiteral("0:00"));
} }
} }
void MainToolBar::onQueueChanged() void MainToolBar::onQueueChanged()
{ {
m_prevBtn->setEnabled(m_queue->canGoPrev()); m_previous->setEnabled(m_queue->canGoPrev());
m_nextBtn->setEnabled(m_queue->canGoNext()); m_next->setEnabled(m_queue->canGoNext());
} }
void MainToolBar::onShuffleToggled(bool checked) void MainToolBar::onShuffleToggled(bool checked)

View File

@@ -7,8 +7,8 @@
#include "../util/icon.hpp" #include "../util/icon.hpp"
#include <QToolBar> #include <QToolBar>
#include <QToolButton>
#include <QLabel> #include <QLabel>
#include <QAction>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
#include <QJsonObject> #include <QJsonObject>
@@ -28,6 +28,9 @@ signals:
void searchToggled(bool visible); void searchToggled(bool visible);
void queueToggled(bool visible); void queueToggled(bool visible);
protected:
void resizeEvent(QResizeEvent *event) override;
private slots: private slots:
void onPlayPause(); void onPlayPause();
void onPrevious(); void onPrevious();
@@ -49,23 +52,19 @@ private:
QobuzBackend *m_backend = nullptr; QobuzBackend *m_backend = nullptr;
PlayQueue *m_queue = nullptr; PlayQueue *m_queue = nullptr;
// Left
QLabel *m_artLabel = nullptr; QLabel *m_artLabel = nullptr;
QLabel *m_trackLabel = nullptr; QLabel *m_trackLabel = nullptr;
QAction *m_previous = nullptr;
// Center QAction *m_playPause = nullptr;
QToolButton *m_prevBtn = nullptr; QAction *m_next = nullptr;
QToolButton *m_playBtn = nullptr; QWidget *m_leftSpacer = nullptr;
QToolButton *m_nextBtn = nullptr;
ClickableSlider *m_progress = nullptr; ClickableSlider *m_progress = nullptr;
QLabel *m_elapsedLabel = nullptr; QLabel *m_position = nullptr;
QLabel *m_totalLabel = nullptr; QWidget *m_rightSpacer = nullptr;
QAction *m_shuffle = nullptr;
// Right
VolumeButton *m_volume = nullptr; VolumeButton *m_volume = nullptr;
QToolButton *m_shuffleBtn = nullptr; QAction *m_queueBtn = nullptr;
QToolButton *m_queueBtn = nullptr; QAction *m_search = nullptr;
QToolButton *m_searchBtn = nullptr;
QNetworkAccessManager *m_nam = nullptr; QNetworkAccessManager *m_nam = nullptr;
QString m_currentArtUrl; QString m_currentArtUrl;