Batch of performance and animation work: shared FadeHelper/MotionPrefs primitives, hover animators, shadow pixmap caching, smooth scrolling, seek time bubble, and widespread component repaint reductions.
146 lines
4.9 KiB
C++
146 lines
4.9 KiB
C++
#include "shadowpixmap.h"
|
|
|
|
#include <QDir>
|
|
#include <QGraphicsBlurEffect>
|
|
#include <QGraphicsPixmapItem>
|
|
#include <QGraphicsScene>
|
|
#include <QImage>
|
|
#include <QPainter>
|
|
#include <QStandardPaths>
|
|
|
|
namespace
|
|
{
|
|
// Width of the uniform, stretchable interior between the rounded corners.
|
|
// Kept small (the centre is a flat solid colour, so 4 logical px is plenty).
|
|
constexpr int kCenter = 4;
|
|
|
|
int borderFor(int blurRadius, int cornerRadius)
|
|
{
|
|
// Falloff reach beyond the object edge, plus the corner radius which lives
|
|
// inside the fixed (non-stretched) border so corners never distort.
|
|
return (blurRadius + 1) + cornerRadius;
|
|
}
|
|
|
|
int totalFor(int blurRadius, int cornerRadius)
|
|
{
|
|
return 2 * borderFor(blurRadius, cornerRadius) + kCenter;
|
|
}
|
|
|
|
QString cacheFilePath(int blurRadius, int cornerRadius, const QColor &color, qreal dpr)
|
|
{
|
|
const QString dir =
|
|
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/shadows";
|
|
QDir().mkpath(dir);
|
|
// dpr folded in at x100 so e.g. 1.5 -> 150; colour as rrggbbaa.
|
|
const QString name = QString("shadow_b%1_c%2_%3_d%4.png")
|
|
.arg(blurRadius)
|
|
.arg(cornerRadius)
|
|
.arg(static_cast<uint>(color.rgba()), 8, 16, QLatin1Char('0'))
|
|
.arg(qRound(dpr * 100));
|
|
return dir + '/' + name;
|
|
}
|
|
|
|
// Render the rounded rect, then blur it off-screen via QGraphicsBlurEffect.
|
|
// Done once per cache key; the cost here is irrelevant to runtime frame rate.
|
|
QImage bake(int blurRadius, int cornerRadius, const QColor &color, qreal dpr)
|
|
{
|
|
const int total = totalFor(blurRadius, cornerRadius);
|
|
const int border = borderFor(blurRadius, cornerRadius);
|
|
const qreal s = dpr;
|
|
const int physTotal = qMax(1, qRound(total * s));
|
|
|
|
QImage shape(physTotal, physTotal, QImage::Format_ARGB32_Premultiplied);
|
|
shape.fill(Qt::transparent);
|
|
{
|
|
QPainter p(&shape);
|
|
p.setRenderHint(QPainter::Antialiasing, true);
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(color);
|
|
// Object footprint: inset by the falloff reach (border - corner) so the
|
|
// blur has room to fade on every side.
|
|
const qreal reach = (border - cornerRadius) * s;
|
|
const qreal objSide = (total - 2 * (border - cornerRadius)) * s;
|
|
p.drawRoundedRect(QRectF(reach, reach, objSide, objSide),
|
|
cornerRadius * s, cornerRadius * s);
|
|
}
|
|
|
|
QGraphicsScene scene;
|
|
auto *item = scene.addPixmap(QPixmap::fromImage(shape));
|
|
auto *blur = new QGraphicsBlurEffect;
|
|
blur->setBlurRadius(blurRadius * s);
|
|
blur->setBlurHints(QGraphicsBlurEffect::QualityHint);
|
|
item->setGraphicsEffect(blur);
|
|
|
|
QImage out(physTotal, physTotal, QImage::Format_ARGB32_Premultiplied);
|
|
out.fill(Qt::transparent);
|
|
{
|
|
QPainter op(&out);
|
|
scene.render(&op, QRectF(0, 0, physTotal, physTotal),
|
|
QRectF(0, 0, physTotal, physTotal));
|
|
}
|
|
return out;
|
|
}
|
|
} // namespace
|
|
|
|
namespace ShadowPixmap
|
|
{
|
|
NinePatchShadow make(int blurRadius, int cornerRadius, const QColor &color, qreal dpr)
|
|
{
|
|
NinePatchShadow result;
|
|
result.border = borderFor(blurRadius, cornerRadius);
|
|
result.total = totalFor(blurRadius, cornerRadius);
|
|
result.dpr = dpr;
|
|
|
|
const QString path = cacheFilePath(blurRadius, cornerRadius, color, dpr);
|
|
|
|
QPixmap cached;
|
|
if (cached.load(path, "PNG") && !cached.isNull())
|
|
{
|
|
result.pixmap = cached;
|
|
return result;
|
|
}
|
|
|
|
const QImage baked = bake(blurRadius, cornerRadius, color, dpr);
|
|
result.pixmap = QPixmap::fromImage(baked);
|
|
baked.save(path, "PNG"); // best-effort cache; failure just re-bakes next time
|
|
return result;
|
|
}
|
|
|
|
void drawNinePatch(QPainter *painter, const QRect &target, const NinePatchShadow &patch)
|
|
{
|
|
if (!painter || patch.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int b = patch.border;
|
|
const int t = patch.total;
|
|
const qreal s = patch.dpr;
|
|
const QRect r = target;
|
|
|
|
// Degenerate target (smaller than the two fixed borders): nothing sensible
|
|
// to slice, skip rather than draw garbage.
|
|
if (r.width() < 2 * b || r.height() < 2 * b)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 3x3 grid: corners fixed, edges/centre stretched. Target columns/rows are
|
|
// logical; source columns/rows are physical (pixmap carries no dpr).
|
|
const int xt[4] = {r.left(), r.left() + b, r.right() + 1 - b, r.right() + 1};
|
|
const int yt[4] = {r.top(), r.top() + b, r.bottom() + 1 - b, r.bottom() + 1};
|
|
const qreal xs[4] = {0.0, b * s, (t - b) * s, t * s};
|
|
const qreal ys[4] = {0.0, b * s, (t - b) * s, t * s};
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
const QRectF targetCell(xt[i], yt[j], xt[i + 1] - xt[i], yt[j + 1] - yt[j]);
|
|
const QRectF sourceCell(xs[i], ys[j], xs[i + 1] - xs[i], ys[j + 1] - ys[j]);
|
|
painter->drawPixmap(targetCell, patch.pixmap, sourceCell);
|
|
}
|
|
}
|
|
}
|
|
} // namespace ShadowPixmap
|