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 <QtQml/qqml.h>
#include <QtQuick/QSGClipNode> #include <QtQuick/QSGClipNode>
#include <QtQuick/QSGFlatColorMaterial>
#include <QtQuick/QSGGeometryNode> #include <QtQuick/QSGGeometryNode>
#include <QtQuick/QSGVertexColorMaterial>
namespace { namespace {
QSGGeometryNode *makeLayer(const QColor &color) QSGGeometryNode *makeLayer()
{ {
auto *node = new QSGGeometryNode; auto *node = new QSGGeometryNode;
auto *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0); auto *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0);
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip); geometry->setDrawingMode(QSGGeometry::DrawTriangles);
node->setGeometry(geometry); node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry); node->setFlag(QSGNode::OwnsGeometry);
node->setMaterial(new QSGVertexColorMaterial);
auto *material = new QSGFlatColorMaterial;
material->setColor(color);
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial); node->setFlag(QSGNode::OwnsMaterial);
return node; return node;
} }
@@ -36,9 +33,9 @@ public:
baseClip.setIsRectangular(true); baseClip.setIsRectangular(true);
cacheClip.setIsRectangular(true); cacheClip.setIsRectangular(true);
playedClip.setIsRectangular(true); playedClip.setIsRectangular(true);
baseLayer = makeLayer(Qt::transparent); baseLayer = makeLayer();
cacheLayer = makeLayer(Qt::transparent); cacheLayer = makeLayer();
playedLayer = makeLayer(Qt::transparent); playedLayer = makeLayer();
baseClip.appendChildNode(baseLayer); baseClip.appendChildNode(baseLayer);
cacheClip.appendChildNode(cacheLayer); cacheClip.appendChildNode(cacheLayer);
playedClip.appendChildNode(playedLayer); playedClip.appendChildNode(playedLayer);
@@ -62,37 +59,95 @@ public:
QSGGeometryNode *playedLayer = nullptr; QSGGeometryNode *playedLayer = nullptr;
quint64 geometryRevision = std::numeric_limits<quint64>::max(); quint64 geometryRevision = std::numeric_limits<quint64>::max();
QSizeF geometrySize; 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()); const float combined = std::clamp(static_cast<float>(color.alphaF()) * alpha, 0.0f, 1.0f);
if (material->color() == color) vertex.set(x,
return; y,
material->setColor(color); static_cast<unsigned char>(std::round(color.red() * combined)),
node->markDirty(QSGNode::DirtyMaterial); 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, void setLayerGeometry(QSGGeometryNode *node,
const std::vector<float> &values, const std::vector<float> &values,
qreal width, qreal width,
qreal height) qreal height,
const QColor &color)
{ {
auto *geometry = node->geometry(); auto *geometry = node->geometry();
const int count = static_cast<int>(values.size()); const int count = static_cast<int>(values.size());
geometry->allocate(count * 2); const int visibleBars = static_cast<int>(std::count_if(values.begin(), values.end(),
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip); [](float value) { return value > 0.0005f; }));
auto *vertices = geometry->vertexDataAsPoint2D(); geometry->allocate(6 + visibleBars * 12);
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
auto *vertices = geometry->vertexDataAsColoredPoint2D();
const float centre = static_cast<float>(height * 0.5); 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) { for (int index = 0; index < count; ++index) {
const float x = count == 1 const float value = std::clamp(values[index], 0.0f, 1.0f);
? 0.0f if (value <= 0.0005f)
: static_cast<float>(width) * index / static_cast<float>(count - 1); continue;
const float amplitude = std::clamp(values[index], 0.0f, 1.0f) * halfHeight; const float amplitude = std::min(halfHeight,
vertices[index * 2].set(x, centre - amplitude); 0.85f + value * std::max(0.0f, halfHeight - 0.85f));
vertices[index * 2 + 1].set(x, centre + amplitude); 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); node->markDirty(QSGNode::DirtyGeometry);
} }
@@ -110,7 +165,7 @@ std::vector<float> renderValues(const QVariantList &source, int columns)
float peak = 0.0f; float peak = 0.0f;
for (int index = begin; index < std::min(end, sourceCount); ++index) for (int index = begin; index < std::min(end, sourceCount); ++index)
peak = std::max(peak, std::clamp(source[index].toFloat(), 0.0f, 1.0f)); 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); 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.front() = values.front();
scratch.back() = values.back(); scratch.back() = values.back();
for (int index = 1; index + 1 < columns; ++index) { for (int index = 1; index + 1 < columns; ++index) {
scratch[index] = values[index - 1] * 0.25f scratch[index] = values[index - 1] * 0.20f
+ values[index] * 0.5f + values[index] * 0.60f
+ values[index + 1] * 0.25f; + values[index + 1] * 0.20f;
} }
values.swap(scratch); 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->cacheClip.setClipRect(QRectF(0.0, 0.0, width() * m_cacheProgress, height()));
root->playedClip.setClipRect(QRectF(0.0, 0.0, width() * m_playedProgress, height())); root->playedClip.setClipRect(QRectF(0.0, 0.0, width() * m_playedProgress, height()));
if (root->geometryRevision != m_valuesRevision || root->geometrySize != size()) { const bool colorsChanged = root->baseGeometryColor != m_baseColor
const int columns = qBound(16, static_cast<int>(std::ceil(width() / 2.0)), 512); || 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); const auto values = renderValues(m_values, columns);
setLayerGeometry(root->baseLayer, values, width(), height()); setLayerGeometry(root->baseLayer, values, width(), height(), m_baseColor);
setLayerGeometry(root->cacheLayer, values, width(), height()); setLayerGeometry(root->cacheLayer, values, width(), height(), m_cacheColor);
setLayerGeometry(root->playedLayer, values, width(), height()); setLayerGeometry(root->playedLayer, values, width(), height(), m_playedColor);
root->geometryRevision = m_valuesRevision; root->geometryRevision = m_valuesRevision;
root->geometrySize = size(); 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; return root;
} }
@@ -219,7 +219,7 @@ Rectangle {
Rectangle { Rectangle {
id: seekTrack id: seekTrack
width: parent.width width: parent.width
height: root.waveformVisible ? 9 : 3 height: root.waveformVisible ? 11 : 3
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
radius: 2 radius: 2
color: root.waveformVisible ? "transparent" : theme.surfaceElevated color: root.waveformVisible ? "transparent" : theme.surfaceElevated
+1 -1
View File
@@ -323,7 +323,7 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.leftMargin: (root.dockWidth > 0 ? root.dockWidth + 6 : 6) + (QbzPlayer.npHasTrack ? 46 : 0) anchors.leftMargin: (root.dockWidth > 0 ? root.dockWidth + 6 : 6) + (QbzPlayer.npHasTrack ? 46 : 0)
anchors.rightMargin: 6 + (QbzPlayer.npHasTrack ? 46 : 0) anchors.rightMargin: 6 + (QbzPlayer.npHasTrack ? 46 : 0)
height: root.waveformVisible ? 14 : 4 height: root.waveformVisible ? 18 : 4
radius: 2 radius: 2
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: root.waveformVisible ? "transparent" : theme.surfaceElevated color: root.waveformVisible ? "transparent" : theme.surfaceElevated