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 <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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user