fix(player): refine immersive lyrics and seekbars
Reuse the split lyrics renderer for full-screen timed fill, add a contrast shadow and expose the immersive song card there. Render the compact seekbar as a stable three-sine motif and make the larger waveform use normalized rounded capsules with stronger peaks. cx
This commit is contained in:
6 files changed
+172
-53
No files matched your search
@@ -97,6 +97,70 @@ void setQuad(QSGGeometry::ColoredPoint2D *vertices,
|
||||
setVertex(vertices[5], right, bottom, color, bottomAlpha);
|
||||
}
|
||||
|
||||
constexpr int capsuleCapSegments = 4;
|
||||
constexpr int capsuleVertexCount = 12 + capsuleCapSegments * 6;
|
||||
|
||||
void setTriangle(QSGGeometry::ColoredPoint2D *vertices,
|
||||
const QPointF &first,
|
||||
const QPointF &second,
|
||||
const QPointF &third,
|
||||
const QColor &color,
|
||||
float firstAlpha,
|
||||
float secondAlpha,
|
||||
float thirdAlpha)
|
||||
{
|
||||
setVertex(vertices[0], static_cast<float>(first.x()), static_cast<float>(first.y()),
|
||||
color, firstAlpha);
|
||||
setVertex(vertices[1], static_cast<float>(second.x()), static_cast<float>(second.y()),
|
||||
color, secondAlpha);
|
||||
setVertex(vertices[2], static_cast<float>(third.x()), static_cast<float>(third.y()),
|
||||
color, thirdAlpha);
|
||||
}
|
||||
|
||||
void setVerticalCapsule(QSGGeometry::ColoredPoint2D *vertices,
|
||||
float left,
|
||||
float top,
|
||||
float right,
|
||||
float bottom,
|
||||
const QColor &color)
|
||||
{
|
||||
constexpr qreal pi = 3.14159265358979323846;
|
||||
const float centreX = (left + right) * 0.5f;
|
||||
const float centreY = (top + bottom) * 0.5f;
|
||||
const float radius = std::min((right - left) * 0.5f, (bottom - top) * 0.5f);
|
||||
const float topCentre = top + radius;
|
||||
const float bottomCentre = bottom - radius;
|
||||
|
||||
setQuad(vertices, left, topCentre, right, centreY, color, 0.62f, 1.0f);
|
||||
setQuad(vertices + 6, left, centreY, right, bottomCentre, color, 1.0f, 0.58f);
|
||||
|
||||
int vertex = 12;
|
||||
for (int segment = 0; segment < capsuleCapSegments; ++segment) {
|
||||
const qreal firstAngle = pi + pi * segment / capsuleCapSegments;
|
||||
const qreal secondAngle = pi + pi * (segment + 1) / capsuleCapSegments;
|
||||
setTriangle(vertices + vertex,
|
||||
QPointF(centreX, topCentre),
|
||||
QPointF(centreX + std::cos(firstAngle) * radius,
|
||||
topCentre + std::sin(firstAngle) * radius),
|
||||
QPointF(centreX + std::cos(secondAngle) * radius,
|
||||
topCentre + std::sin(secondAngle) * radius),
|
||||
color, 0.62f, 0.42f, 0.42f);
|
||||
vertex += 3;
|
||||
}
|
||||
for (int segment = 0; segment < capsuleCapSegments; ++segment) {
|
||||
const qreal firstAngle = pi * segment / capsuleCapSegments;
|
||||
const qreal secondAngle = pi * (segment + 1) / capsuleCapSegments;
|
||||
setTriangle(vertices + vertex,
|
||||
QPointF(centreX, bottomCentre),
|
||||
QPointF(centreX + std::cos(firstAngle) * radius,
|
||||
bottomCentre + std::sin(firstAngle) * radius),
|
||||
QPointF(centreX + std::cos(secondAngle) * radius,
|
||||
bottomCentre + std::sin(secondAngle) * radius),
|
||||
color, 0.58f, 0.38f, 0.38f);
|
||||
vertex += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void setLayerGeometry(QSGGeometryNode *node,
|
||||
const std::vector<float> &values,
|
||||
qreal width,
|
||||
@@ -110,10 +174,10 @@ void setLayerGeometry(QSGGeometryNode *node,
|
||||
const float halfHeight = std::max(1.0f, static_cast<float>(height * 0.47));
|
||||
|
||||
if (renderMode == 1) {
|
||||
// Small NPB: the RMS document has no signed samples, so present its
|
||||
// loudness contour as one continuous, centre-crossing curve. A few
|
||||
// dozen well-spaced segments read as a waveform at 11 px; hundreds
|
||||
// of sub-pixel bars only read as a rough blue rail.
|
||||
// Small NPB: this is a stable three-sine motif rather than decoded
|
||||
// RMS. At thirteen pixels the real envelope cannot communicate track
|
||||
// structure, while a smooth repeated contour remains readable and
|
||||
// still carries played/cache clipping as a seek affordance.
|
||||
geometry->allocate(6 + std::max(0, count - 1) * 6);
|
||||
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
|
||||
auto *vertices = geometry->vertexDataAsColoredPoint2D();
|
||||
@@ -162,7 +226,7 @@ void setLayerGeometry(QSGGeometryNode *node,
|
||||
|
||||
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->allocate(6 + visibleBars * capsuleVertexCount);
|
||||
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
|
||||
auto *vertices = geometry->vertexDataAsColoredPoint2D();
|
||||
|
||||
@@ -178,13 +242,13 @@ void setLayerGeometry(QSGGeometryNode *node,
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int groupSize = 8;
|
||||
constexpr int groupSize = 12;
|
||||
const int groupBreaks = (count - 1) / groupSize;
|
||||
const float nominalSlot = static_cast<float>(width) / count;
|
||||
const float groupGap = std::min(2.8f, std::max(1.1f, nominalSlot * 0.52f));
|
||||
const float groupGap = std::min(2.4f, std::max(0.9f, nominalSlot * 0.34f));
|
||||
const float usableWidth = std::max(1.0f, static_cast<float>(width) - groupBreaks * groupGap);
|
||||
const float slot = usableWidth / count;
|
||||
const float barWidth = std::max(1.2f, std::min(slot * 0.74f, slot - 0.35f));
|
||||
const float barWidth = std::clamp(slot * 0.56f, 1.8f, 3.6f);
|
||||
int vertex = 6;
|
||||
|
||||
for (int index = 0; index < count; ++index) {
|
||||
@@ -196,12 +260,9 @@ void setLayerGeometry(QSGGeometryNode *node,
|
||||
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;
|
||||
setVerticalCapsule(vertices + vertex, left, centre - amplitude,
|
||||
right, centre + amplitude, color);
|
||||
vertex += capsuleVertexCount;
|
||||
}
|
||||
node->markDirty(QSGNode::DirtyGeometry);
|
||||
}
|
||||
@@ -233,6 +294,30 @@ std::vector<float> renderValues(const QVariantList &source, int columns, float g
|
||||
}
|
||||
values.swap(scratch);
|
||||
}
|
||||
const float maximum = *std::max_element(values.begin(), values.end());
|
||||
if (maximum > 0.0005f) {
|
||||
for (float &value : values)
|
||||
value = std::clamp(value / maximum, 0.0f, 1.0f);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
std::vector<float> decorativeWaveValues(int columns, qreal width)
|
||||
{
|
||||
std::vector<float> values(columns, 0.5f);
|
||||
if (columns < 2)
|
||||
return values;
|
||||
|
||||
constexpr qreal twoPi = 6.28318530717958647692;
|
||||
const qreal repeats = std::max<qreal>(3.0, width / 210.0);
|
||||
for (int index = 0; index < columns; ++index) {
|
||||
const qreal progress = static_cast<qreal>(index) / (columns - 1);
|
||||
const qreal phase = twoPi * repeats * progress;
|
||||
const qreal composite = 0.55 * std::sin(phase)
|
||||
+ 0.28 * std::sin(phase * 2.0 + 0.85)
|
||||
+ 0.17 * std::sin(phase * 3.0 + 2.1);
|
||||
values[index] = static_cast<float>(0.5 + composite * 0.46);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
@@ -307,7 +392,7 @@ void SeekWaveformItem::setRenderMode(int mode)
|
||||
|
||||
QSGNode *SeekWaveformItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
|
||||
{
|
||||
if (m_values.isEmpty() || width() <= 0.0 || height() <= 0.0) {
|
||||
if ((m_values.isEmpty() && m_renderMode == 0) || width() <= 0.0 || height() <= 0.0) {
|
||||
delete oldNode;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -327,9 +412,11 @@ QSGNode *SeekWaveformItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData
|
||||
if (root->geometryRevision != m_valuesRevision || root->geometrySize != size()
|
||||
|| colorsChanged || root->geometryRenderMode != m_renderMode) {
|
||||
const int columns = m_renderMode == 1
|
||||
? qBound(32, static_cast<int>(std::ceil(width() / 9.0)), 192)
|
||||
: qBound(40, static_cast<int>(std::ceil(width() / 5.5)), 256);
|
||||
const auto values = renderValues(m_values, columns, m_renderMode == 1 ? 0.9f : 0.78f);
|
||||
? qBound(64, static_cast<int>(std::ceil(width() / 3.0)), 512)
|
||||
: qBound(48, static_cast<int>(std::ceil(width() / 7.5)), 240);
|
||||
const auto values = m_renderMode == 1
|
||||
? decorativeWaveValues(columns, width())
|
||||
: renderValues(m_values, columns, 0.72f);
|
||||
setLayerGeometry(root->baseLayer, values, width(), height(), m_baseColor, m_renderMode);
|
||||
setLayerGeometry(root->cacheLayer, values, width(), height(), m_cacheColor, m_renderMode);
|
||||
setLayerGeometry(root->playedLayer, values, width(), height(), m_playedColor, m_renderMode);
|
||||
|
||||
Reference in new issue
Block a user