diff --git a/src/view/queuepanel.cpp b/src/view/queuepanel.cpp index 0504560..a37203d 100644 --- a/src/view/queuepanel.cpp +++ b/src/view/queuepanel.cpp @@ -4,10 +4,106 @@ #include #include #include +#include +#include +#include static constexpr int UpcomingIndexRole = Qt::UserRole + 1; static constexpr int IsPlayNextRole = Qt::UserRole + 2; static constexpr int TrackJsonRole = Qt::UserRole + 3; +static constexpr int ArtistRole = Qt::UserRole + 4; +static constexpr int DurationRole = Qt::UserRole + 5; + +// ---- Custom delegate ------------------------------------------------------- + +class QueueDelegate : public QStyledItemDelegate +{ +public: + explicit QueueDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} + + QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override + { + return QSize(0, 52); + } + + void paint(QPainter *p, const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + p->save(); + + // Background (selection / hover / alternate) + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget); + + const bool isPlayNext = index.data(IsPlayNextRole).toBool(); + const QString title = index.data(Qt::DisplayRole).toString(); + const QString artist = index.data(ArtistRole).toString(); + const int dur = index.data(DurationRole).toInt(); + + const QRect r = option.rect.adjusted(10, 0, -10, 0); + + // Duration string, right-aligned + QString durStr; + if (dur > 0) { + const int m = dur / 60, s = dur % 60; + durStr = QStringLiteral("%1:%2").arg(m).arg(s, 2, 10, QLatin1Char('0')); + } + + // Colours + const QPalette &pal = option.palette; + const bool selected = option.state & QStyle::State_Selected; + QColor titleColor = selected ? pal.highlightedText().color() : pal.text().color(); + QColor artistColor = titleColor; + artistColor.setAlpha(150); + QColor durColor = artistColor; + + if (isPlayNext && !selected) { + titleColor = titleColor.lighter(130); + } + + // Fonts + QFont titleFont = option.font; + titleFont.setWeight(QFont::Medium); + + QFont subFont = option.font; + subFont.setPointSizeF(option.font.pointSizeF() * 0.85); + + // Layout: title row vertically centred in top half, artist in bottom half + const int halfH = r.height() / 2; + const QRect topR = QRect(r.left(), r.top(), r.width(), halfH); + const QRect botR = QRect(r.left(), r.top() + halfH, r.width(), halfH); + + // Duration — draw right-aligned in top row + if (!durStr.isEmpty()) { + p->setFont(subFont); + p->setPen(durColor); + p->drawText(topR, Qt::AlignRight | Qt::AlignVCenter, durStr); + } + + // Reserve space for duration in title row + const int durW = durStr.isEmpty() ? 0 + : QFontMetrics(subFont).horizontalAdvance(durStr) + 6; + const QRect titleR = topR.adjusted(0, 0, -durW, 0); + + // Title + p->setFont(titleFont); + p->setPen(titleColor); + p->drawText(titleR, Qt::AlignLeft | Qt::AlignVCenter, + p->fontMetrics().elidedText(title, Qt::ElideRight, titleR.width())); + + // Artist + if (!artist.isEmpty()) { + p->setFont(subFont); + p->setPen(artistColor); + p->drawText(botR, Qt::AlignLeft | Qt::AlignVCenter, + p->fontMetrics().elidedText(artist, Qt::ElideRight, botR.width())); + } + + p->restore(); + } +}; + +// ---- QueuePanel ------------------------------------------------------------ QueuePanel::QueuePanel(PlayQueue *queue, QWidget *parent) : QDockWidget(tr("Queue"), parent) @@ -21,7 +117,6 @@ QueuePanel::QueuePanel(PlayQueue *queue, QWidget *parent) layout->setContentsMargins(4, 4, 4, 4); layout->setSpacing(4); - // Header row: count label + Clear button auto *headerRow = new QHBoxLayout; m_countLabel = new QLabel(tr("Up next: 0 tracks"), container); m_clearBtn = new QPushButton(tr("Clear"), container); @@ -35,21 +130,17 @@ QueuePanel::QueuePanel(PlayQueue *queue, QWidget *parent) m_list->setContextMenuPolicy(Qt::CustomContextMenu); m_list->setDragDropMode(QAbstractItemView::InternalMove); m_list->setDefaultDropAction(Qt::MoveAction); + m_list->setItemDelegate(new QueueDelegate(m_list)); layout->addWidget(m_list, 1); setWidget(container); setMinimumWidth(200); connect(m_queue, &PlayQueue::queueChanged, this, &QueuePanel::refresh); - connect(m_clearBtn, &QPushButton::clicked, this, [this] { - m_queue->clearUpcoming(); - }); - connect(m_list, &QListWidget::itemDoubleClicked, - this, &QueuePanel::onItemDoubleClicked); - connect(m_list, &QListWidget::customContextMenuRequested, - this, &QueuePanel::onContextMenu); - connect(m_list->model(), &QAbstractItemModel::rowsMoved, - this, &QueuePanel::onRowsMoved); + connect(m_clearBtn, &QPushButton::clicked, this, [this] { m_queue->clearUpcoming(); }); + connect(m_list, &QListWidget::itemDoubleClicked, this, &QueuePanel::onItemDoubleClicked); + connect(m_list, &QListWidget::customContextMenuRequested, this, &QueuePanel::onContextMenu); + connect(m_list->model(), &QAbstractItemModel::rowsMoved, this, &QueuePanel::onRowsMoved); refresh(); } @@ -73,22 +164,14 @@ void QueuePanel::refresh() const QString artist = t["performer"].toObject()["name"].toString().isEmpty() ? t["album"].toObject()["artist"].toObject()["name"].toString() : t["performer"].toObject()["name"].toString(); + const int duration = t["duration"].toInt(); - const QString text = artist.isEmpty() - ? title - : QStringLiteral("%1 — %2").arg(artist, title); - - auto *item = new QListWidgetItem(text, m_list); + auto *item = new QListWidgetItem(title, m_list); item->setData(UpcomingIndexRole, i); item->setData(IsPlayNextRole, i < playNextCount); item->setData(TrackJsonRole, QVariant::fromValue(t)); - - // "Play Next" tracks shown slightly differently - if (i < playNextCount) { - QFont f = item->font(); - f.setItalic(true); - item->setFont(f); - } + item->setData(ArtistRole, artist); + item->setData(DurationRole, duration); } m_refreshing = false; @@ -109,10 +192,8 @@ void QueuePanel::onRowsMoved() QVector newOrder; newOrder.reserve(m_list->count()); - for (int i = 0; i < m_list->count(); ++i) { - const QVariant v = m_list->item(i)->data(TrackJsonRole); - newOrder.append(v.value()); - } + for (int i = 0; i < m_list->count(); ++i) + newOrder.append(m_list->item(i)->data(TrackJsonRole).value()); m_refreshing = true; m_queue->setUpcomingOrder(newOrder); @@ -127,15 +208,11 @@ void QueuePanel::onContextMenu(const QPoint &pos) const int idx = item->data(UpcomingIndexRole).toInt(); QMenu menu(this); - auto *removeAct = menu.addAction(tr("Remove from queue")); - auto *toTopAct = menu.addAction(tr("Move to top (play next)")); + auto *removeAct = menu.addAction(tr("Remove from queue")); + auto *toTopAct = menu.addAction(tr("Move to top (play next)")); - connect(removeAct, &QAction::triggered, this, [this, idx] { - m_queue->removeUpcoming(idx); - }); - connect(toTopAct, &QAction::triggered, this, [this, idx] { - m_queue->moveUpcomingToTop(idx); - }); + connect(removeAct, &QAction::triggered, this, [this, idx] { m_queue->removeUpcoming(idx); }); + connect(toTopAct, &QAction::triggered, this, [this, idx] { m_queue->moveUpcomingToTop(idx); }); menu.exec(m_list->viewport()->mapToGlobal(pos)); }