perf(ui): replace animated-sidebar drop shadows with pre-baked 9-patch overlay

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.
This commit is contained in:
dela
2026-06-18 17:03:57 +08:00
parent a608910148
commit 907de1feeb
8 changed files with 394 additions and 36 deletions
+46 -8
View File
@@ -8,6 +8,7 @@
#include "../../components/playerosdlayer.h"
#include "../../components/playerstatisticsoverlay.h"
#include "../../components/playersubtitlesettingsdialog.h"
#include "../../components/shadowoverlay.h"
#include "../../utils/mediaitemutils.h"
#include "../../utils/mediasourcepreferenceutils.h"
#include "../../utils/playerpreferenceutils.h"
@@ -26,7 +27,6 @@
#include <QFileDialog>
#include <QFileInfo>
#include <QFontMetrics>
#include <QGraphicsDropShadowEffect>
#include <QHBoxLayout>
#include <QListWidget>
#include <QMainWindow>
@@ -1611,6 +1611,8 @@ void PlayerView::setupUi()
updateDanmakuButtonState();
updateMediaSwitcherButton();
m_rightSidebar->raise();
if (m_rightSidebarShadow)
m_rightSidebarShadow->stackUnder(m_rightSidebar);
}
@@ -1633,9 +1635,11 @@ void PlayerView::updateLoadingState()
m_topHUD->raise();
m_bottomHUD->raise();
m_logoLabel->raise();
m_networkSpeedLabel->raise();
m_logoLabel->raise();
m_networkSpeedLabel->raise();
m_rightSidebar->raise();
if (m_rightSidebarShadow)
m_rightSidebarShadow->stackUnder(m_rightSidebar);
m_rightTrigger->raise();
if (m_activePopup)
m_activePopup->raise();
@@ -2224,11 +2228,15 @@ void PlayerView::setupRightSidebar()
m_rightSidebar->setFixedWidth(220);
m_rightSidebar->setGeometry(20000, 0, 220, 100);
auto *shadow = new QGraphicsDropShadowEffect(this);
shadow->setBlurRadius(35);
shadow->setColor(QColor(0, 0, 0, 150));
shadow->setOffset(-5, 0);
m_rightSidebar->setGraphicsEffect(shadow);
// Drop shadow via a pre-baked 9-patch overlay instead of
// QGraphicsDropShadowEffect, which would re-run a software Gaussian blur on
// every frame of the 350ms slide. Body cutout is on because the sidebar
// background is semi-transparent (the shadow must not bleed through it).
// The overlay tracks the sidebar's geometry in syncRightSidebarShadow().
m_rightSidebarShadow = new ShadowOverlay(this);
m_rightSidebarShadow->setShadow(35, 0, QColor(0, 0, 0, 150));
m_rightSidebarShadow->setBodyCutout(true);
m_rightSidebarShadow->hide();
auto *layout = new QVBoxLayout(m_rightSidebar);
layout->setContentsMargins(0, 15, 0, 15);
@@ -2374,6 +2382,26 @@ void PlayerView::hideRightSidebar(bool immediate)
m_rightSidebarAnim->start();
}
void PlayerView::syncRightSidebarShadow()
{
if (!m_rightSidebarShadow || !m_rightSidebar)
{
return;
}
if (!m_rightSidebar->isVisible())
{
m_rightSidebarShadow->hide();
return;
}
const int reach = m_rightSidebarShadow->reach();
m_rightSidebarShadow->setGeometry(
m_rightSidebar->geometry().adjusted(-reach, -reach, reach, reach));
m_rightSidebarShadow->stackUnder(m_rightSidebar);
m_rightSidebarShadow->show();
}
void PlayerView::stopAndReport()
@@ -2874,6 +2902,16 @@ bool PlayerView::eventFilter(QObject *watched, QEvent *event)
return BaseView::eventFilter(watched, event);
}
// Keep the drop-shadow overlay glued to the sidebar. The slide animation
// and layout both move/resize it, landing here as Move/Resize events, so the
// shadow follows every frame with no per-frame blur.
if (watched == m_rightSidebar &&
(event->type() == QEvent::Move || event->type() == QEvent::Resize ||
event->type() == QEvent::Show || event->type() == QEvent::Hide))
{
syncRightSidebarShadow();
}
if (watched == m_mpvWidget || watched == this || watched == m_topHUD || watched == m_titleLabel ||
watched == m_bottomHUD)
{