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:
@@ -0,0 +1,90 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef SHADOWOVERLAY_H
|
||||
#define SHADOWOVERLAY_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QWidget>
|
||||
|
||||
#include "../utils/shadowpixmap.h"
|
||||
|
||||
// A mouse-transparent sibling widget that paints a pre-baked drop shadow as a
|
||||
// 9-slice stretch. Use it in place of QGraphicsDropShadowEffect on widgets that
|
||||
// animate: position the overlay just under the target (geometry expanded by
|
||||
// reach() on every side) and it follows with no per-frame blur cost.
|
||||
//
|
||||
// The baked pixmap is built lazily on first paint and rebuilt only when the
|
||||
// device pixel ratio changes, so HiDPI / monitor moves stay crisp.
|
||||
class ShadowOverlay : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ShadowOverlay(QWidget *parent = nullptr);
|
||||
|
||||
// Configure the shadow. blurRadius/cornerRadius are logical pixels; color
|
||||
// carries the tint and its alpha sets strength. Invalidates any baked pixmap.
|
||||
void setShadow(int blurRadius, int cornerRadius, const QColor &color);
|
||||
|
||||
// How far the shadow extends beyond the target on each side. Callers expand
|
||||
// the target geometry by this amount to get the overlay geometry.
|
||||
int reach() const { return m_blurRadius + 1; }
|
||||
|
||||
// When enabled, the target's footprint (the overlay rect inset by reach()) is
|
||||
// clipped out, so the shadow is painted only as a ring around the target.
|
||||
// Required for semi-transparent targets, where the shadow would otherwise
|
||||
// bleed through the body and darken it.
|
||||
void setBodyCutout(bool on);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
void ensurePixmap(qreal dpr);
|
||||
|
||||
int m_blurRadius = 0;
|
||||
int m_cornerRadius = 0;
|
||||
QColor m_color;
|
||||
bool m_bodyCutout = false;
|
||||
|
||||
NinePatchShadow m_patch;
|
||||
qreal m_builtDpr = 0.0;
|
||||
};
|
||||
|
||||
#endif // SHADOWOVERLAY_H
|
||||
@@ -0,0 +1,108 @@
|
||||
#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;
|
||||
}
|
||||
} // namespace ShadowPixmap
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef SHADOWPIXMAP_H
|
||||
#define SHADOWPIXMAP_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QPixmap>
|
||||
|
||||
// Pre-baked, blurred drop-shadow rendered as a 9-patch pixmap.
|
||||
//
|
||||
// Drawing a QGraphicsDropShadowEffect forces the host widget through an
|
||||
// off-screen render + software Gaussian blur on every repaint, which is the
|
||||
// main cause of jank while a shadowed widget is animated. Instead we bake the
|
||||
// blur exactly once (cached to disk as PNG), then render it at runtime as a
|
||||
// cheap 9-slice stretch with zero per-frame blur cost.
|
||||
//
|
||||
// The baked image represents a rounded rectangle of side (total - 2*border)
|
||||
// surrounded by `border` pixels of blurred falloff on every edge. `border` is
|
||||
// expressed in logical (device-independent) pixels; the pixmap itself is baked
|
||||
// at `dpr` physical pixels per logical pixel and carries NO devicePixelRatio,
|
||||
// so callers must scale source rectangles by `dpr` when slicing.
|
||||
struct NinePatchShadow
|
||||
{
|
||||
QPixmap pixmap; // physical-resolution bitmap, devicePixelRatio() == 1
|
||||
int border = 0; // fixed (non-stretched) edge width, logical px
|
||||
int total = 0; // full pixmap side, logical px
|
||||
qreal dpr = 1.0; // physical-per-logical scale the pixmap was baked at
|
||||
|
||||
bool isNull() const { return pixmap.isNull(); }
|
||||
};
|
||||
|
||||
namespace ShadowPixmap
|
||||
{
|
||||
// Build (or load from the on-disk cache) a 9-patch shadow for a rounded rect.
|
||||
// blurRadius/cornerRadius are logical pixels; color carries the shadow tint and
|
||||
// its alpha controls overall strength. Results are cached per
|
||||
// (blurRadius, cornerRadius, color, dpr) under CacheLocation/shadows.
|
||||
NinePatchShadow make(int blurRadius, int cornerRadius, const QColor &color, qreal dpr);
|
||||
}
|
||||
|
||||
#endif // SHADOWPIXMAP_H
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1636,6 +1638,8 @@ void PlayerView::updateLoadingState()
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
class QEmbyCore;
|
||||
class PlayerOverlayDialog;
|
||||
class ShadowOverlay;
|
||||
class PlayerMediaSwitcherPanel;
|
||||
class PlayerOsdLayer;
|
||||
class PlayerLongPressHandler;
|
||||
@@ -138,6 +139,7 @@ private:
|
||||
long long startPositionTicks);
|
||||
|
||||
void hideRightSidebar(bool immediate = false);
|
||||
void syncRightSidebarShadow();
|
||||
void setEffectivePlaybackSpeed(double speed);
|
||||
void handlePointerActivity(const QPoint &globalPos);
|
||||
void setCursorHidden(bool hidden);
|
||||
@@ -207,6 +209,7 @@ private:
|
||||
|
||||
|
||||
QWidget *m_rightSidebar;
|
||||
ShadowOverlay *m_rightSidebarShadow = nullptr;
|
||||
QWidget *m_rightTrigger;
|
||||
QPropertyAnimation *m_rightSidebarAnim;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../../components/downloadmanagerdialog.h"
|
||||
#include "../../components/elidedlabel.h"
|
||||
#include "../../components/moderntoast.h"
|
||||
#include "../../components/shadowoverlay.h"
|
||||
#include "../../components/slidingstackedwidget.h"
|
||||
#include "../../components/webdavsyncdialog.h"
|
||||
#include "../../managers/thememanager.h"
|
||||
@@ -31,7 +32,6 @@
|
||||
#include <QDebug>
|
||||
#include <QEvent>
|
||||
#include <QFrame>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHideEvent>
|
||||
#include <QLabel>
|
||||
@@ -608,14 +608,14 @@ void HomeView::setupSidebar()
|
||||
m_sidebar->setProperty("sidebarSide", m_sidebarOnRight ? "right" : "left");
|
||||
|
||||
|
||||
if (!m_sidebarPinned)
|
||||
{
|
||||
auto *shadow = new QGraphicsDropShadowEffect(this);
|
||||
shadow->setBlurRadius(25);
|
||||
shadow->setColor(QColor(0, 0, 0, 30));
|
||||
shadow->setOffset(m_sidebarOnRight ? -4 : 4, 0);
|
||||
m_sidebar->setGraphicsEffect(shadow);
|
||||
}
|
||||
// Drop shadow as a mouse-transparent sibling painted via a pre-baked
|
||||
// 9-patch, instead of QGraphicsDropShadowEffect. The effect would force the
|
||||
// sidebar through an off-screen software blur on every frame of its slide
|
||||
// animation; the overlay just follows the sidebar's geometry (see
|
||||
// syncSidebarShadow) with no per-frame blur cost.
|
||||
m_sidebarShadow = new ShadowOverlay(this);
|
||||
m_sidebarShadow->setShadow(25, 0, QColor(0, 0, 0, 30));
|
||||
m_sidebarShadow->hide();
|
||||
|
||||
auto *layout = new QVBoxLayout(m_sidebar);
|
||||
layout->setContentsMargins(16, 20, 0, 20);
|
||||
@@ -1616,6 +1616,16 @@ bool HomeView::eventFilter(QObject *watched, QEvent *event)
|
||||
|
||||
if (watched == m_sidebar)
|
||||
{
|
||||
// The drop-shadow overlay tracks the sidebar's geometry. move()/
|
||||
// setGeometry() anywhere (resize, slide animation, reposition) lands
|
||||
// here as a Move/Resize event, so the shadow follows every frame
|
||||
// without any per-frame blur.
|
||||
if (event->type() == QEvent::Move || event->type() == QEvent::Resize ||
|
||||
event->type() == QEvent::Show || event->type() == QEvent::Hide)
|
||||
{
|
||||
syncSidebarShadow();
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Leave)
|
||||
{
|
||||
QPoint globalPos = QCursor::pos();
|
||||
@@ -1988,6 +1998,30 @@ void HomeView::syncSidebarVisibility()
|
||||
m_edgeTrigger->show();
|
||||
}
|
||||
|
||||
void HomeView::syncSidebarShadow()
|
||||
{
|
||||
if (!m_sidebarShadow || !m_sidebar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// No floating shadow when pinned (docked in layout) or while the sidebar is
|
||||
// hidden (e.g. immersive views). The sidebar staying on-screen but slid off
|
||||
// to a negative/overflow position keeps the overlay shown but equally
|
||||
// off-screen, so it correctly tracks slide-in/out.
|
||||
if (m_sidebarPinned || !m_sidebar->isVisible())
|
||||
{
|
||||
m_sidebarShadow->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const int reach = m_sidebarShadow->reach();
|
||||
m_sidebarShadow->setGeometry(
|
||||
m_sidebar->geometry().adjusted(-reach, -reach, reach, reach));
|
||||
m_sidebarShadow->stackUnder(m_sidebar);
|
||||
m_sidebarShadow->show();
|
||||
}
|
||||
|
||||
void HomeView::applySidebarPosition()
|
||||
{
|
||||
m_sidebar->setProperty("sidebarSide", m_sidebarOnRight ? "right" : "left");
|
||||
@@ -2012,13 +2046,8 @@ void HomeView::applySidebarPosition()
|
||||
|
||||
const int sidebarW = m_sidebar->width();
|
||||
|
||||
|
||||
if (auto *shadow = qobject_cast<QGraphicsDropShadowEffect *>(m_sidebar->graphicsEffect()))
|
||||
{
|
||||
shadow->setOffset(m_sidebarOnRight ? -4 : 4, 0);
|
||||
}
|
||||
|
||||
|
||||
// Shadow side follows the sidebar automatically (overlay tracks geometry in
|
||||
// syncSidebarShadow); the baked shadow is symmetric, so no per-side offset.
|
||||
if (m_sidebarOnRight)
|
||||
{
|
||||
m_edgeTrigger->setGeometry(width() - kRightEdgeTriggerWidth, 0, kRightEdgeTriggerWidth, height());
|
||||
@@ -2056,8 +2085,9 @@ void HomeView::applySidebarPinned(bool pinned)
|
||||
m_sidebarAnim->stop();
|
||||
m_sidebarAutoHideTimer->stop();
|
||||
|
||||
|
||||
m_sidebar->setGraphicsEffect(nullptr);
|
||||
// Pinned sidebar is docked into the layout and casts no floating shadow.
|
||||
if (m_sidebarShadow)
|
||||
m_sidebarShadow->hide();
|
||||
|
||||
|
||||
m_sidebar->setProperty("pinned", true);
|
||||
@@ -2087,12 +2117,8 @@ void HomeView::applySidebarPinned(bool pinned)
|
||||
m_contentLayout->removeWidget(m_sidebar);
|
||||
m_sidebar->setParent(this);
|
||||
|
||||
|
||||
auto *shadow = new QGraphicsDropShadowEffect(this);
|
||||
shadow->setBlurRadius(25);
|
||||
shadow->setColor(QColor(0, 0, 0, 30));
|
||||
shadow->setOffset(m_sidebarOnRight ? -4 : 4, 0);
|
||||
m_sidebar->setGraphicsEffect(shadow);
|
||||
// Floating sidebar regains its drop shadow (painted by the overlay
|
||||
// sibling; geometry is synced in syncSidebarShadow / eventFilter).
|
||||
|
||||
|
||||
m_sidebar->setProperty("pinned", false);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
class QTimer;
|
||||
class QEmbyCore;
|
||||
class SlidingStackedWidget;
|
||||
class ShadowOverlay;
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QAction;
|
||||
@@ -102,6 +103,7 @@ private:
|
||||
void applySidebarIcons();
|
||||
void applySidebarCustomVisibility();
|
||||
void syncSidebarVisibility();
|
||||
void syncSidebarShadow();
|
||||
void openCloudSyncDialog();
|
||||
void setupSearchHistory();
|
||||
void updateSearchCompleter(const QString &text = QString());
|
||||
@@ -137,6 +139,7 @@ private:
|
||||
FavoritesView* m_favoritesView = nullptr;
|
||||
|
||||
QWidget* m_sidebar = nullptr;
|
||||
ShadowOverlay* m_sidebarShadow = nullptr;
|
||||
QWidget* m_edgeTrigger = nullptr;
|
||||
QPropertyAnimation* m_sidebarAnim = nullptr;
|
||||
QTimer* m_sidebarAutoHideTimer = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user