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:
3 files changed
+101
-43
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user