fix(player): refine seekbar waveform rendering

Replace the continuous strip with symmetric gradient bars, periodic spacing, and a persistent centre rail. Use more of the available seekbar height while preserving played and cached clipping in the native scene graph.\n\ncx
This commit is contained in:
vicrodh committed 2026-08-25 02:51:41 -06:00
1 parent ef7ea217c8
commit f56cd00244
3 files changed
+101 -43

No files matched your search

+99 -41
View File
@@ -8,22 +8,19 @@
#include <QtQml/qqml.h>
#include <QtQuick/QSGClipNode>
#include <QtQuick/QSGFlatColorMaterial>
#include <QtQuick/QSGGeometryNode>
#include <QtQuick/QSGVertexColorMaterial>
namespace {
QSGGeometryNode *makeLayer(const QColor &color)
QSGGeometryNode *makeLayer()
{
auto *node = new QSGGeometryNode;
auto *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0);
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
auto *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0);
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
auto *material = new QSGFlatColorMaterial;
material->setColor(color);
node->setMaterial(material);
node->setMaterial(new QSGVertexColorMaterial);
node->setFlag(QSGNode::OwnsMaterial);
return node;
}
@@ -36,9 +33,9 @@ public:
baseClip.setIsRectangular(true);
cacheClip.setIsRectangular(true);
playedClip.setIsRectangular(true);
baseLayer = makeLayer(Qt::transparent);
cacheLayer = makeLayer(Qt::transparent);
playedLayer = makeLayer(Qt::transparent);
baseLayer = makeLayer();
cacheLayer = makeLayer();
playedLayer = makeLayer();
baseClip.appendChildNode(baseLayer);
cacheClip.appendChildNode(cacheLayer);
playedClip.appendChildNode(playedLayer);
@@ -62,37 +59,95 @@ public:
QSGGeometryNode *playedLayer = nullptr;
quint64 geometryRevision = std::numeric_limits<quint64>::max();
QSizeF geometrySize;
QColor baseGeometryColor;
QColor cacheGeometryColor;
QColor playedGeometryColor;
};
void setLayerColor(QSGGeometryNode *node, const QColor &color)
void setVertex(QSGGeometry::ColoredPoint2D &vertex,
float x,
float y,
const QColor &color,
float alpha)
{
auto *material = static_cast<QSGFlatColorMaterial *>(node->material());
if (material->color() == color)
return;
material->setColor(color);
node->markDirty(QSGNode::DirtyMaterial);
const float combined = std::clamp(static_cast<float>(color.alphaF()) * alpha, 0.0f, 1.0f);
vertex.set(x,
y,
static_cast<unsigned char>(std::round(color.red() * combined)),
static_cast<unsigned char>(std::round(color.green() * combined)),
static_cast<unsigned char>(std::round(color.blue() * combined)),
static_cast<unsigned char>(std::round(255.0f * combined)));
}
void setQuad(QSGGeometry::ColoredPoint2D *vertices,
float left,
float top,
float right,
float bottom,
const QColor &color,
float topAlpha,
float bottomAlpha)
{
setVertex(vertices[0], left, top, color, topAlpha);
setVertex(vertices[1], left, bottom, color, bottomAlpha);
setVertex(vertices[2], right, top, color, topAlpha);
setVertex(vertices[3], right, top, color, topAlpha);
setVertex(vertices[4], left, bottom, color, bottomAlpha);
setVertex(vertices[5], right, bottom, color, bottomAlpha);
}
void setLayerGeometry(QSGGeometryNode *node,
const std::vector<float> &values,
qreal width,
qreal height)
qreal height,
const QColor &color)
{
auto *geometry = node->geometry();
const int count = static_cast<int>(values.size());
geometry->allocate(count * 2);
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
auto *vertices = geometry->vertexDataAsPoint2D();
const int visibleBars = static_cast<int>(std::count_if(values.begin(), values.end(),
[](float value) { return value > 0.0005f; }));
geometry->allocate(6 + visibleBars * 12);
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
auto *vertices = geometry->vertexDataAsColoredPoint2D();
const float centre = static_cast<float>(height * 0.5);
const float halfHeight = std::max(1.0f, static_cast<float>(height * 0.48));
const float halfHeight = std::max(1.0f, static_cast<float>(height * 0.47));
// A permanent hairline makes the temporal axis readable before the whole
// track has been analysed. Its three clipped color layers retain the
// existing played/cache/base semantics.
const float railHalf = std::min(0.55f, halfHeight * 0.16f);
setQuad(vertices, 0.0f, centre - railHalf, static_cast<float>(width), centre + railHalf,
color, 0.46f, 0.46f);
if (count == 0) {
node->markDirty(QSGNode::DirtyGeometry);
return;
}
constexpr int groupSize = 8;
const int groupBreaks = (count - 1) / groupSize;
const float nominalSlot = static_cast<float>(width) / count;
const float groupGap = std::min(1.7f, std::max(0.65f, nominalSlot * 0.48f));
const float usableWidth = std::max(1.0f, static_cast<float>(width) - groupBreaks * groupGap);
const float slot = usableWidth / count;
const float barWidth = std::max(0.65f, std::min(slot * 0.66f, slot - 0.18f));
int vertex = 6;
for (int index = 0; index < count; ++index) {
const float x = count == 1
? 0.0f
: static_cast<float>(width) * index / static_cast<float>(count - 1);
const float amplitude = std::clamp(values[index], 0.0f, 1.0f) * halfHeight;
vertices[index * 2].set(x, centre - amplitude);
vertices[index * 2 + 1].set(x, centre + amplitude);
const float value = std::clamp(values[index], 0.0f, 1.0f);
if (value <= 0.0005f)
continue;
const float amplitude = std::min(halfHeight,
0.85f + value * std::max(0.0f, halfHeight - 0.85f));
const float left = slot * index + groupGap * (index / groupSize)
+ (slot - barWidth) * 0.5f;
const float right = left + barWidth;
setQuad(vertices + vertex, left, centre - amplitude, right, centre,
color, 0.48f, 0.98f);
vertex += 6;
setQuad(vertices + vertex, left, centre, right, centre + amplitude,
color, 0.98f, 0.42f);
vertex += 6;
}
node->markDirty(QSGNode::DirtyGeometry);
}
@@ -110,7 +165,7 @@ std::vector<float> renderValues(const QVariantList &source, int columns)
float peak = 0.0f;
for (int index = begin; index < std::min(end, sourceCount); ++index)
peak = std::max(peak, std::clamp(source[index].toFloat(), 0.0f, 1.0f));
values[column] = peak * peak;
values[column] = std::pow(peak, 1.42f);
}
std::vector<float> scratch(columns, 0.0f);
@@ -118,9 +173,9 @@ std::vector<float> renderValues(const QVariantList &source, int columns)
scratch.front() = values.front();
scratch.back() = values.back();
for (int index = 1; index + 1 < columns; ++index) {
scratch[index] = values[index - 1] * 0.25f
+ values[index] * 0.5f
+ values[index + 1] * 0.25f;
scratch[index] = values[index - 1] * 0.20f
+ values[index] * 0.60f
+ values[index + 1] * 0.20f;
}
values.swap(scratch);
}
@@ -203,19 +258,22 @@ QSGNode *SeekWaveformItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData
root->cacheClip.setClipRect(QRectF(0.0, 0.0, width() * m_cacheProgress, height()));
root->playedClip.setClipRect(QRectF(0.0, 0.0, width() * m_playedProgress, height()));
if (root->geometryRevision != m_valuesRevision || root->geometrySize != size()) {
const int columns = qBound(16, static_cast<int>(std::ceil(width() / 2.0)), 512);
const bool colorsChanged = root->baseGeometryColor != m_baseColor
|| root->cacheGeometryColor != m_cacheColor
|| root->playedGeometryColor != m_playedColor;
if (root->geometryRevision != m_valuesRevision || root->geometrySize != size()
|| colorsChanged) {
const int columns = qBound(24, static_cast<int>(std::ceil(width() / 2.45)), 512);
const auto values = renderValues(m_values, columns);
setLayerGeometry(root->baseLayer, values, width(), height());
setLayerGeometry(root->cacheLayer, values, width(), height());
setLayerGeometry(root->playedLayer, values, width(), height());
setLayerGeometry(root->baseLayer, values, width(), height(), m_baseColor);
setLayerGeometry(root->cacheLayer, values, width(), height(), m_cacheColor);
setLayerGeometry(root->playedLayer, values, width(), height(), m_playedColor);
root->geometryRevision = m_valuesRevision;
root->geometrySize = size();
root->baseGeometryColor = m_baseColor;
root->cacheGeometryColor = m_cacheColor;
root->playedGeometryColor = m_playedColor;
}
setLayerColor(root->baseLayer, m_baseColor);
setLayerColor(root->cacheLayer, m_cacheColor);
setLayerColor(root->playedLayer, m_playedColor);
return root;
}
@@ -219,7 +219,7 @@ Rectangle {
Rectangle {
id: seekTrack
width: parent.width
height: root.waveformVisible ? 9 : 3
height: root.waveformVisible ? 11 : 3
anchors.verticalCenter: parent.verticalCenter
radius: 2
color: root.waveformVisible ? "transparent" : theme.surfaceElevated
+1 -1
View File
@@ -323,7 +323,7 @@ Rectangle {
anchors.right: parent.right
anchors.leftMargin: (root.dockWidth > 0 ? root.dockWidth + 6 : 6) + (QbzPlayer.npHasTrack ? 46 : 0)
anchors.rightMargin: 6 + (QbzPlayer.npHasTrack ? 46 : 0)
height: root.waveformVisible ? 14 : 4
height: root.waveformVisible ? 18 : 4
radius: 2
anchors.verticalCenter: parent.verticalCenter
color: root.waveformVisible ? "transparent" : theme.surfaceElevated