QGraphicsDropShadowEffect forces the host widget through an off-screen software Gaussian blur on every repaint. On the floating sidebar (HomeView, blur 25) and the player right sidebar (PlayerView, blur 35), the blur was re-rasterized on every frame of the 350ms slide animation, the main source of UI jank. Bake the blur once into a 9-patch PNG (cached to CacheLocation/shadows via QGraphicsBlurEffect) and render it at runtime as a cheap 9-slice stretch with zero per-frame blur cost: - utils/shadowpixmap: off-screen bake + on-disk cache, dpr-aware. - components/shadowoverlay: mouse-transparent sibling widget, lazy-baked, paints the 9-patch; optional body cutout for semi-transparent targets so the shadow does not bleed through the body. - homeview/playerview: drop the effect; the overlay tracks the sidebar geometry via the existing eventFilter (Move/Resize/Show/Hide), so every setGeometry/move site and the slide animation drive it for free.
91 lines
2.5 KiB
C++
91 lines
2.5 KiB
C++
#include "shadowoverlay.h"
|
|
|
|
#include <QPainter>
|
|
|
|
ShadowOverlay::ShadowOverlay(QWidget *parent) : QWidget(parent)
|
|
{
|
|
setAttribute(Qt::WA_TransparentForMouseEvents, true);
|
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
|
setFocusPolicy(Qt::NoFocus);
|
|
}
|
|
|
|
void ShadowOverlay::setShadow(int blurRadius, int cornerRadius, const QColor &color)
|
|
{
|
|
m_blurRadius = blurRadius;
|
|
m_cornerRadius = cornerRadius;
|
|
m_color = color;
|
|
m_patch = NinePatchShadow{}; // force rebuild on next paint
|
|
m_builtDpr = 0.0;
|
|
update();
|
|
}
|
|
|
|
void ShadowOverlay::setBodyCutout(bool on)
|
|
{
|
|
if (m_bodyCutout == on)
|
|
{
|
|
return;
|
|
}
|
|
m_bodyCutout = on;
|
|
update();
|
|
}
|
|
|
|
void ShadowOverlay::ensurePixmap(qreal dpr)
|
|
{
|
|
if (!m_patch.isNull() && qFuzzyCompare(m_builtDpr, dpr))
|
|
{
|
|
return;
|
|
}
|
|
m_patch = ShadowPixmap::make(m_blurRadius, m_cornerRadius, m_color, dpr);
|
|
m_builtDpr = dpr;
|
|
}
|
|
|
|
void ShadowOverlay::paintEvent(QPaintEvent *)
|
|
{
|
|
const qreal dpr = devicePixelRatioF();
|
|
ensurePixmap(dpr);
|
|
if (m_patch.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int b = m_patch.border;
|
|
const int t = m_patch.total;
|
|
const qreal s = m_patch.dpr;
|
|
const QRect r = rect();
|
|
|
|
// 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};
|
|
|
|
QPainter p(this);
|
|
if (m_bodyCutout)
|
|
{
|
|
// Paint only the ring outside the target's footprint.
|
|
const int reachPx = reach();
|
|
const QRect body = r.adjusted(reachPx, reachPx, -reachPx, -reachPx);
|
|
if (body.isValid())
|
|
{
|
|
p.setClipRegion(QRegion(r).subtracted(QRegion(body)));
|
|
}
|
|
}
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
const QRectF target(xt[i], yt[j], xt[i + 1] - xt[i], yt[j + 1] - yt[j]);
|
|
const QRectF source(xs[i], ys[j], xs[i + 1] - xs[i], ys[j + 1] - ys[j]);
|
|
p.drawPixmap(target, m_patch.pixmap, source);
|
|
}
|
|
}
|
|
}
|